////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   Area.cpp
//  Version:     v1.00
//  Created:     27/9/2004 by Timur.
//  Compilers:   Visual Studio.NET
//  Description: 
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "Area.h"
#include <IRenderAuxGeom.h>
#include "ISound.h" // for pSound->GetId()

//////////////////////////////////////////////////////////////////////////
CArea::CArea( CAreaManager *pManager ):
m_VOrigin(0.0f),
m_VSize(0.0f),
m_PrevFade(-1.0f),
m_fProximity(5.0f),
m_fMaximumEffectRadius(0.0f),
m_AreaGroupID(-1),
m_nPriority(0),
m_AreaID(-1),
m_stepID(-1),
m_EntityID(0),
m_BoxMin(0),
m_BoxMax(0),
m_SphereCenter(0),
m_SphereRadius(0),
m_SphereRadius2(0),
m_bIsActive(false),
m_bObstructRoof(false),
m_bObstructFloor(false),
m_bEntityIdsResolved(false),
m_bAllObstructed(0),
m_nObstructedCount(0)
{
	m_AreaType = ENTITY_AREA_TYPE_SHAPE;
	m_InvMatrix.SetIdentity();
	m_WorldTM.SetIdentity();
	m_pAreaManager = pManager;
	m_bInitialized = false;
	m_bHasSoundAttached = false;
	m_bAttachedSoundTested = false;

	// All sides not obstructed by default
	memset(&m_abBoxSideObstruction, 0, 6);
}


//////////////////////////////////////////////////////////////////////////
CArea::~CArea(void)
{
	ClearEntities();
	ClearPoints();
	m_pAreaManager->Unregister(this);
}

//////////////////////////////////////////////////////////////////////////
void CArea::Release()
{
	delete this;
}

//////////////////////////////////////////////////////////////////////////
void CArea::SetSoundObstructionOnAreaFace( int unsigned const nFaceIndex, bool const bObstructs )
{
	switch (m_AreaType)
	{
	case ENTITY_AREA_TYPE_BOX:
		{
			m_abBoxSideObstruction[nFaceIndex].bObstructed = bObstructs?1:0;

			m_nObstructedCount = 0;
			for (unsigned int i = 0; i < 6; ++i)
			{
				if (m_abBoxSideObstruction[i].bObstructed)
					++m_nObstructedCount;
			}

			m_bAllObstructed = 0;
			if (m_nObstructedCount == 6)
				m_bAllObstructed = 1;
		}
		break;
	case ENTITY_AREA_TYPE_SHAPE:
		{
			unsigned int const nSegmentCount = m_vpSegments.size();
			if (nFaceIndex < nSegmentCount)
				m_vpSegments[nFaceIndex]->bObstructSound = bObstructs;
			else
			{
				// We exceed segment count which could mean
				// that the user wants to set roof and floor sound obstruction
				if (nFaceIndex == nSegmentCount)
				{
					// The user wants to set roof sound obstruction
					m_bObstructRoof = bObstructs;
				}
				else if (nFaceIndex == nSegmentCount+1)
				{
					// The user wants to set floor sound obstruction
					m_bObstructFloor = bObstructs;
				}
			}
		}
		break;
	}
}

//////////////////////////////////////////////////////////////////////////
void CArea::SetAreaType( EEntityAreaType type ) 
{ 
	m_AreaType = type; 
	
	// a bit hacky way to prevent gravityvolumes being evaluated in the
	// AreaManager::UpdatePlayer function, that caused problems.
	if (m_AreaType == ENTITY_AREA_TYPE_GRAVITYVOLUME)
		m_pAreaManager->Unregister(this);

} 

// resets area - clears all segments in area
//////////////////////////////////////////////////////////////////////////
void	CArea::ClearPoints()
{
	a2DSegment*	carSegment;

	for(unsigned int sIdx=0; sIdx<m_vpSegments.size(); sIdx++)
	{
		carSegment = m_vpSegments[sIdx];
		delete carSegment;
	}
	m_vpSegments.clear();
	m_bInitialized = false;
}

//////////////////////////////////////////////////////////////////////////
unsigned CArea::MemStat()
{
	unsigned memSize = sizeof *this;

	memSize += m_vpSegments.size()*(sizeof(a2DSegment) + sizeof(a2DSegment*)) ;
	return memSize;	
}

//adds segment to area, calculates line parameters y=kx+b, sets horizontal/vertical flags
//////////////////////////////////////////////////////////////////////////
void	CArea::AddSegment(const a2DPoint& p0, const a2DPoint& p1, bool const bObstructSound)
{
	a2DSegment*	newSegment			= new a2DSegment;
	newSegment->bObstructSound	= bObstructSound;

	//if this is horizontal line set flag. This segment is needed only for distance calculations
	if(p1.y == p0.y)
		newSegment->isHorizontal = true;
	else
		newSegment->isHorizontal = false;

	if( p0.x < p1.x )
	{
		newSegment->bbox.min.x = p0.x;
		newSegment->bbox.max.x = p1.x;
	}
	else
	{
		newSegment->bbox.min.x = p1.x;
		newSegment->bbox.max.x = p0.x;
	}

	if( p0.y < p1.y )
	{
		newSegment->bbox.min.y = p0.y;
		newSegment->bbox.max.y = p1.y;
	}
	else
	{
		newSegment->bbox.min.y = p1.y;
		newSegment->bbox.max.y = p0.y;
	}

	if(!newSegment->isHorizontal)
	{
		//if this is vertical line - spesial case
		if(p1.x == p0.x)
		{
			newSegment->k = 0;
			newSegment->b = p0.x;
		}
		else
		{
			newSegment->k = (p1.y - p0.y)/(p1.x - p0.x);
			newSegment->b = p0.y - newSegment->k*p0.x;
		}
	}
	else
	{
		newSegment->k = 0;
		newSegment->b = 0;
	}
	m_vpSegments.push_back( newSegment );
}

// calculates min distance from point within area to the border of area
// returns fade coefficient: Distance/m_Proximity
// [0 - on the very border of area,	1 - inside area, distance to border is more than m_Proximity]
//////////////////////////////////////////////////////////////////////////	
float	CArea::CalcDistToPoint(const a2DPoint& point) const
{
	if (!m_bInitialized)
		return -1;

	if (m_fProximity == 0.0f)
		return 1.0f;

	float	distMin = m_fProximity*m_fProximity;
	float	curDist;
	a2DBBox	proximityBox;

	proximityBox.max.x = point.x + m_fProximity;
	proximityBox.max.y = point.y + m_fProximity;
	proximityBox.min.x = point.x - m_fProximity;
	proximityBox.min.y = point.y - m_fProximity;

	for(unsigned int sIdx=0; sIdx<m_vpSegments.size(); sIdx++)
	{
		a2DSegment *curSg = m_vpSegments[sIdx];

		if(!m_vpSegments[sIdx]->bbox.BBoxOutBBox2D( proximityBox ))
		{
			if(m_vpSegments[sIdx]->isHorizontal)
			{
				if( point.x<m_vpSegments[sIdx]->bbox.min.x )
					curDist = m_vpSegments[sIdx]->bbox.min.DistSqr( point );
				else if( point.x>m_vpSegments[sIdx]->bbox.max.x )
					curDist = m_vpSegments[sIdx]->bbox.max.DistSqr( point );
				else
					curDist = fabsf( point.y-m_vpSegments[sIdx]->bbox.max.y );
				curDist *= curDist;
			}
			else
			{
				if(m_vpSegments[sIdx]->k==0.0f)
				{
					if( point.y<m_vpSegments[sIdx]->bbox.min.y )
						curDist = m_vpSegments[sIdx]->bbox.min.DistSqr( point );
					else if( point.y>m_vpSegments[sIdx]->bbox.max.y )
						curDist = m_vpSegments[sIdx]->bbox.max.DistSqr( point );
					else
						curDist = fabsf( point.x-m_vpSegments[sIdx]->b );
					curDist *= curDist;
				}
				else
				{
					a2DPoint	intersection;
					float	b2, k2;
					k2 = -1.0f/m_vpSegments[sIdx]->k;
					b2 = point.y - k2*point.x;
					intersection.x = (b2 - m_vpSegments[sIdx]->b)/(m_vpSegments[sIdx]->k - k2);
					intersection.y = k2*intersection.x + b2;

					if( intersection.x<m_vpSegments[sIdx]->bbox.min.x)
						if( m_vpSegments[sIdx]->k<0 )
							curDist = point.DistSqr( m_vpSegments[sIdx]->bbox.min.x, m_vpSegments[sIdx]->bbox.max.y );
						else
							curDist = point.DistSqr( m_vpSegments[sIdx]->bbox.min );
					else if( intersection.x>m_vpSegments[sIdx]->bbox.max.x)
						if( m_vpSegments[sIdx]->k<0 )
							curDist = point.DistSqr( m_vpSegments[sIdx]->bbox.max.x, m_vpSegments[sIdx]->bbox.min.y );
						else
							curDist = point.DistSqr( m_vpSegments[sIdx]->bbox.max );
					else
						curDist = intersection.DistSqr( point );
				}
				if(curDist<distMin)
					distMin = curDist;	
			}
		}
	}

	return cry_sqrtf(distMin)/m_fProximity; // TODO: Check if "distmin/m_fProximity*m_fProximity" is faster
}

// check if the point is within the area
// first BBox check, then count number of intersections for horizontal ray from point and area segments
// if the number is odd - the point is inside
//////////////////////////////////////////////////////////////////////////
bool CArea::CalcPointWithin(const Vec3& point3d, bool bIgnoreHeight/* =false */, bool const bCacheResult /* = true */)
{
	if (!m_bInitialized)
		return false;

	if( m_AreaType == ENTITY_AREA_TYPE_SPHERE )
	{
		Vec3	sPnt = point3d - m_SphereCenter;

		if (bIgnoreHeight)
			sPnt.z = 0;

		// Set the flags and put the result into the data cache
		if (bCacheResult)
		{
			if (bIgnoreHeight)
				m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PointWithinValid | eCachedAreaData_PointWithinValidHeightIgnored);
			else
				m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PointWithinValid);

			m_oCachedAreaData.bPointWithin = (sPnt.GetLengthSquared() < m_SphereRadius2);
		}
		else
			return (sPnt.GetLengthSquared() < m_SphereRadius2);

		return m_oCachedAreaData.bPointWithin;
	}
	else if( m_AreaType == ENTITY_AREA_TYPE_BOX )
	{
		Vec3 p3d = m_InvMatrix.TransformPoint(point3d);

		if (bIgnoreHeight)
			p3d.z = m_BoxMax.z;

		// Set the flags
		if (bCacheResult)
		{
			if (bIgnoreHeight)
				m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PointWithinValid | eCachedAreaData_PointWithinValidHeightIgnored);
			else
				m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PointWithinValid);
		}

		// And put the result into the data cache
		if ((p3d.x<m_BoxMin.x) ||
				(p3d.y<m_BoxMin.y) ||
				(p3d.z<m_BoxMin.z) ||
				(p3d.x>m_BoxMax.x) ||
				(p3d.y>m_BoxMax.y) ||
				(p3d.z>m_BoxMax.z))
		{
			if (bCacheResult)
				m_oCachedAreaData.bPointWithin = false;
			else
				return false;
		}
		else
		{
			if (bCacheResult)
				m_oCachedAreaData.bPointWithin = true;
			else
				return true;
		}

		return m_oCachedAreaData.bPointWithin;
	}
	else if( m_AreaType == ENTITY_AREA_TYPE_SHAPE )
	{
		if (!bIgnoreHeight)
		{
			if( m_VSize>0.0f )
				if( point3d.z<m_VOrigin || point3d.z>m_VOrigin + m_VSize )
					return false;
		}

		a2DPoint	*point = (CArea::a2DPoint*)(&point3d);

		if( m_AreaBBox.PointOutBBox2D( *point ) )
			return false;

		int	cntr=0;	

		unsigned int const nSegmentCount = m_vpSegments.size();
		for(unsigned int sIdx=0; sIdx<nSegmentCount; ++sIdx)
		{
			if(m_vpSegments[sIdx]->isHorizontal)
				continue;

			if(!m_vpSegments[sIdx]->bbox.PointOutBBox2DVertical( *point ))
			{
				if(	m_vpSegments[sIdx]->IntersectsXPosVertical( *point ) ||
						m_vpSegments[sIdx]->IntersectsXPos( *point ))
					cntr++;
			}
		}

		// Set the flags and put the result into the data cache
		if (bCacheResult)
		{
			if (bIgnoreHeight)
				m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PointWithinValid | eCachedAreaData_PointWithinValidHeightIgnored);
			else
				m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PointWithinValid);

			m_oCachedAreaData.bPointWithin	= (cntr & 0x1);
		}
		else
			return (cntr & 0x1);

		return m_oCachedAreaData.bPointWithin;
	}

	// to keep the compiler happy and give more robustness to the code with the assert...
	assert(0); // can never reach this part
	return false; // default behavior
}

//	for editor use - if point is within - returns min hor distance to border
//	if point out - returns -1
//////////////////////////////////////////////////////////////////////////
float	CArea::CalcPointWithinDist(const Vec3& point3d, bool const bIgnoreSoundObstruction /* = true */, bool const bCacheResult /* = true */)
{
	float	minDist = -1.0f;

	// only computes distance if point is not inside
	if(	m_bInitialized && m_oCachedAreaData.eFlags & eCachedAreaData_PosTypeValid &&
			m_oCachedAreaData.ePosType == AREA_POS_TYPE_2DINSIDE_ZINSIDE)
	{
		switch(m_AreaType)
		{
		case ENTITY_AREA_TYPE_SPHERE:
			{
				Vec3	sPnt = point3d - m_SphereCenter;
				float fPointLengthSq = sPnt.GetLengthSquared();

				minDist = m_SphereRadius - sPnt.GetLength();

				// Set the flags and put the shortest distance into the data cache
				if (bCacheResult)
				{
					if (bIgnoreSoundObstruction)
						m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistWithinSqValid | eCachedAreaData_DistWithinSqValidNotObstructed);
					else
						m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistWithinSqValid);
		
					m_oCachedAreaData.fDistanceWithinSq	= m_SphereRadius2 - fPointLengthSq;
				}

				return minDist;
			}
			break;
		case ENTITY_AREA_TYPE_BOX:
			{
				if(bIgnoreSoundObstruction)
				{
					// This does not return correct values if point is outside
					Vec3 vOnHull;

					minDist	= ClosestPointOnHullDistSq(point3d, vOnHull, true);

					// Put the shortest distance into the data cache and set the flags
					if (bCacheResult)
					{
						m_oCachedAreaData.fDistanceWithinSq	= minDist;
						m_oCachedAreaData.eFlags						= (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistWithinSqValid | eCachedAreaData_DistWithinSqValidNotObstructed);
					}

					return cry_sqrtf(minDist);
				}
				else
				{
					Vec3 v3ClosestPos;
					CalcClosestPointToObstructedBox(v3ClosestPos, minDist, point3d);

					// Put the shortest distance into the data cache and set the flags
					if (bCacheResult)
					{
						m_oCachedAreaData.fDistanceWithinSq	= minDist;
						m_oCachedAreaData.eFlags						= (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistWithinSqValid);
					}

					return (minDist > 0.0f)? cry_sqrtf(minDist) : minDist;
				}
			}
			break;
		case ENTITY_AREA_TYPE_SHAPE:
			{
				if(bIgnoreSoundObstruction)
				{
					/*if (!CalcPointWithin(point3d))
						return minDist;*/

					//a2DPoint	*point = (CArea::a2DPoint*)(&point3d);

					//if( m_AreaBBox.PointOutBBox2D( point ) )
							//return -1;

					/*
							for(unsigned int sIdx=0; sIdx<m_vpSegments.size(); sIdx++)
							{
							if(m_vpSegments[sIdx]->isHorizontal)
							continue;
							if( m_vpSegments[sIdx]->k==0 )
							dist = m_vpSegments[sIdx]->b - point->x;
							else
							if(!m_vpSegments[sIdx]->bbox.PointOutBBox2D( *point ))
							dist = m_vpSegments[sIdx]->GetIntersectX( *point );
							else
							continue;
							if( dist<0 )
							dist = -dist;
							else
							cntr++;
							if( minDist>dist || minDist<0)
							minDist = dist;
							}
							*/

					// check distance to every line segment, remember the closest

					unsigned int nSegmentSize = m_vpSegments.size();
					for(unsigned int sIdx=0; sIdx < nSegmentSize; sIdx++)
					{
						float fT;
						a2DSegment *curSg = m_vpSegments[sIdx];

						Vec3 startSeg(curSg->GetStart().x, curSg->GetStart().y, point3d.z);
						Vec3 endSeg(curSg->GetEnd().x, curSg->GetEnd().y, point3d.z);
						Lineseg line(startSeg, endSeg);

				/// Returns distance from a point to a line segment, ignoring the z coordinates
						float fDist = Distance::Point_Lineseg2D(point3d, line, fT);
						
						if(minDist == -1)
							minDist = fDist;

						minDist = min(fDist, minDist);
					}

					if (m_VSize > 0.0f)
					{
						float fDistToRoof = minDist + 1.0f;
						float fDistToFloor = minDist + 1.0f;
						
						if (!m_bObstructFloor)
							fDistToFloor = point3d.z - m_VOrigin;

						if (!m_bObstructRoof)
							fDistToRoof = m_VOrigin + m_VSize - point3d.z;

						float	fZDist = min(fDistToFloor, fDistToRoof);
						minDist = min(minDist, fZDist);
					}

					// Set the flags and put the shortest distance and point into the data cache
					if (bCacheResult)
						m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistWithinSqValid | eCachedAreaData_DistWithinSqValidNotObstructed);
				}
				else
				{
					Vec3 v3ClosestPos;
					CalcClosestPointToObstructedShape(v3ClosestPos, minDist, point3d);

					if(minDist > 0.0f)
						minDist = cry_sqrtf(minDist);

					// Set the flags and put the shortest distance and point into the data cache
					if (bCacheResult)
						m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistWithinSqValid);
				}

				if (bCacheResult)
					m_oCachedAreaData.fDistanceWithinSq	= minDist * minDist;
			}
			break;
		}
	}

	return	minDist;
}

//////////////////////////////////////////////////////////////////////////
float const CArea::ClosestPointOnHullDistSq(const Vec3 &Point3d, Vec3 &OnHull3d, bool const bIgnoreSoundObstruction /* = true */, bool const bCacheResult /* = true */)
{
	if (!m_bInitialized)
		return -1.0f;

	float fClosestDistance = FLT_MAX;
	Vec3 Closest3d(0);

	switch(m_AreaType)
	{
	case	 ENTITY_AREA_TYPE_SHAPE:
		{
			if(bIgnoreSoundObstruction)
			{
//			a2DPoint	*point2d = (CArea::a2DPoint*)(&Point3d);

				//float fLowDist = max(Point3d.z, m_VOrigin) - Point3d.z;
				float fDistToRoof = 0.0f;
				float fDistToFloor = 0.0f;
				float fZDistTemp = 0.0f;

				if (m_VSize)
				{
					// negative means from inside to hull
					fDistToRoof		= Point3d.z - (m_VOrigin+m_VSize);
					fDistToFloor	= m_VOrigin - Point3d.z;

					if (fabsf(fDistToFloor) < fabsf(fDistToRoof))
					{
						// below
						if (m_bObstructFloor)
						{
							fDistToFloor = 0.0f;
							fZDistTemp = fDistToRoof;
						}
						else
						{
							fDistToRoof = 0.0f;
							fZDistTemp = fDistToFloor;
						}
					}
					else
					{
						// above
						if (m_bObstructRoof)
						{
							fDistToRoof = 0.0f;
							fZDistTemp = fDistToFloor;
						}
						else
						{
							fDistToFloor = 0.0f;
							fZDistTemp = fDistToRoof;
						}
					}

					//fHighDist = Point3d.z - min(Point3d.z, m_VOrigin+m_VSize);
				}

				float fZDistSq = fZDistTemp*fZDistTemp;
				//float fZDistSq = (max(fLowDist, fHighDist)*max(fLowDist, fHighDist));
				float fXDistSq = 0.0f;

				bool bIsIn2DShape = false;
				if(m_oCachedAreaData.eFlags & eCachedAreaData_PosTypeValid)
					bIsIn2DShape = (m_oCachedAreaData.ePosType == AREA_POS_TYPE_2DINSIDE_ZABOVE)
								|| (m_oCachedAreaData.ePosType == AREA_POS_TYPE_2DINSIDE_ZINSIDE)
								|| (m_oCachedAreaData.ePosType == AREA_POS_TYPE_2DINSIDE_ZBELOW);
				else
					bIsIn2DShape = (CalcCachedPointPosType(Point3d) == AREA_POS_TYPE_2DINSIDE_ZINSIDE);
				
				//// point is not under or above area shape, so approach from the side
				// Find the line segment that is closest to the 2d point.
				for(unsigned int sIdx=0; sIdx<m_vpSegments.size(); sIdx++)
				{
					float fT;
					a2DSegment *curSg = m_vpSegments[sIdx];

					Vec3 startSeg(curSg->GetStart().x, curSg->GetStart().y, Point3d.z);
					Vec3 endSeg(curSg->GetEnd().x, curSg->GetEnd().y, Point3d.z);
					Lineseg line(startSeg, endSeg);

					/// Returns distance from a point to a line segment, ignoring the z coordinates
					fXDistSq = Distance::Point_Lineseg2DSq(Point3d, line, fT);

					float fThisDistance = 0.0f;

					if (bIsIn2DShape && fZDistSq)
						fThisDistance = min(fXDistSq, fZDistSq);
					else
						fThisDistance = fXDistSq+fZDistSq;

					// is this closer than the previous one?
					if (fThisDistance < fClosestDistance)
					{
						fClosestDistance = fThisDistance;
						// find closest point
						if (fZDistSq && fZDistSq < fXDistSq)
						{
							Closest3d = Point3d;
							Closest3d.z = Point3d.z + fDistToFloor - fDistToRoof;
						}
						else
							Closest3d = (line.GetPoint(fT));

					}
				}

				OnHull3d = Closest3d;

				// Set the flags
				if (bCacheResult)
					m_oCachedAreaData.eFlags	= (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistWithinSqValid | eCachedAreaData_DistWithinSqValidNotObstructed);
			}
			else
			{
				CalcClosestPointToObstructedShape(OnHull3d, fClosestDistance, Point3d);

				// Set the flags
				if (bCacheResult)
					m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistWithinSqValid);
			}

			// Put the shortest distance and point into the data cache
			if (bCacheResult)
			{
				m_oCachedAreaData.v3Pos							= OnHull3d;
				m_oCachedAreaData.fDistanceWithinSq	= fClosestDistance;
			}

			break;
		}

	case	 ENTITY_AREA_TYPE_SPHERE:
		{
			Vec3 Temp = Point3d - m_SphereCenter;
			OnHull3d = Temp.normalize() * m_SphereRadius;
			OnHull3d += m_SphereCenter;
			fClosestDistance = OnHull3d.GetSquaredDistance(Point3d);

			// Set the flags and put the shortest distance and point into the data cache
			if (bCacheResult)
			{
				if (bIgnoreSoundObstruction)
					m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistWithinSqValid | eCachedAreaData_DistWithinSqValidNotObstructed);
				else
					m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistWithinSqValid);

				m_oCachedAreaData.v3Pos							= OnHull3d;
				m_oCachedAreaData.fDistanceWithinSq	= fClosestDistance;
			}

			return fClosestDistance;

			break;
		}
	case	 ENTITY_AREA_TYPE_BOX:
		{
			if(bIgnoreSoundObstruction)
			{
				Vec3 p3d = m_InvMatrix.TransformPoint(Point3d);

				//Matrix33 back = m_InvMatrix.Invert();
				AABB myAABB(m_BoxMin, m_BoxMax);
				//OBB myOBB = OBB::CreateOBBfromAABB(m_InvMatrix.Invert(), myAABB);

				fClosestDistance = Distance::Point_AABBSq(p3d, myAABB, OnHull3d);

				if (m_abBoxSideObstruction[4].bObstructed && OnHull3d.z == myAABB.max.z)
				{
					// Point is on the roof plane, but may be on the edge already
					Vec2 vTop(OnHull3d.x, myAABB.max.y);
					Vec2 vLeft(myAABB.min.x, OnHull3d.y);
					Vec2 vLow(OnHull3d.x, myAABB.min.y);
					Vec2 vRight(myAABB.max.x, OnHull3d.y);

					float fDistanceToTop		= p3d.GetSquaredDistance2D(vTop);
					float fDistanceToLeft		= p3d.GetSquaredDistance2D(vLeft);
					float fDistanceToLow		= p3d.GetSquaredDistance2D(vLow);
					float fDistanceToRight	= p3d.GetSquaredDistance2D(vRight);
					float fTempMinDistance = fDistanceToTop;

					OnHull3d = vTop;

					if (fDistanceToLeft < fTempMinDistance)
					{
						OnHull3d = vLeft;
						fTempMinDistance = fDistanceToLeft;
					}

					if (fDistanceToLow < fTempMinDistance)
					{
						OnHull3d = vLow;
						fTempMinDistance = fDistanceToLow;
					}

					if (fDistanceToRight < fTempMinDistance)
					{
						OnHull3d = vRight;
						fTempMinDistance = fDistanceToRight;
					}

					OnHull3d.z = min(myAABB.max.z, p3d.z);
					fClosestDistance = OnHull3d.GetSquaredDistance(p3d);

				}

				if (m_abBoxSideObstruction[5].bObstructed && OnHull3d.z == myAABB.min.z)
				{
					// Point is on the roof plane, but may be on the edge already
					Vec2 vTop(OnHull3d.x, myAABB.max.y);
					Vec2 vLeft(myAABB.min.x, OnHull3d.y);
					Vec2 vLow(OnHull3d.x, myAABB.min.y);
					Vec2 vRight(myAABB.max.x, OnHull3d.y);

					float fDistanceToTop		= p3d.GetSquaredDistance2D(vTop);
					float fDistanceToLeft		= p3d.GetSquaredDistance2D(vLeft);
					float fDistanceToLow		= p3d.GetSquaredDistance2D(vLow);
					float fDistanceToRight	= p3d.GetSquaredDistance2D(vRight);
					float fTempMinDistance = fDistanceToTop;

					OnHull3d = vTop;

					if (fDistanceToLeft < fTempMinDistance)
					{
						OnHull3d = vLeft;
						fTempMinDistance = fDistanceToLeft;
					}

					if (fDistanceToLow < fTempMinDistance)
					{
						OnHull3d = vLow;
						fTempMinDistance = fDistanceToLow;
					}

					if (fDistanceToRight < fTempMinDistance)
					{
						OnHull3d = vRight;
						fTempMinDistance = fDistanceToRight;
					}

					OnHull3d.z = max(myAABB.min.z, p3d.z);
					fClosestDistance = OnHull3d.GetSquaredDistance(p3d);
				}

				OnHull3d = m_WorldTM.TransformPoint(OnHull3d);

				// Set the flags
				if (bCacheResult)
					m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistWithinSqValid | eCachedAreaData_DistWithinSqValidNotObstructed);
			}
			else
			{
				CalcClosestPointToObstructedBox(OnHull3d, fClosestDistance, Point3d);

				// Set the flags
				if (bCacheResult)
					m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistWithinSqValid);
			}

			// Put the shortest distance and point into the data cache
			if (bCacheResult)
			{
				m_oCachedAreaData.v3Pos							= OnHull3d;
				m_oCachedAreaData.fDistanceWithinSq	= fClosestDistance;
			}

			// TODO transform point back to world
			break;
		}
	}

	return fClosestDistance;

}

//////////////////////////////////////////////////////////////////////////
float CArea::CalcPointNearDistSq(const Vec3 &Point3d, Vec3 &OnHull3d, bool const bIgnoreSoundObstruction /* = true */, bool const bCacheResult /* = true */)
{
	if (!m_bInitialized)
		return -1;

	float fClosestDistance = FLT_MAX;
	Vec3 Closest3d(0);

	switch(m_AreaType)
	{
	case ENTITY_AREA_TYPE_SHAPE:
		{
			if(bIgnoreSoundObstruction)
			{
				float fZDistSq = 0.0f;
				float fXDistSq = FLT_MAX;

				// first find the closest edge
				{
					unsigned int nSegmentSize = m_vpSegments.size();
					for(unsigned int sIdx=0; sIdx<nSegmentSize; sIdx++)
					{
						float fT;
						a2DSegment *curSg = m_vpSegments[sIdx];

						Vec3 startSeg(curSg->GetStart().x, curSg->GetStart().y, Point3d.z);
						Vec3 endSeg(curSg->GetEnd().x, curSg->GetEnd().y, Point3d.z);
						Lineseg line(startSeg, endSeg);

						/// Returns distance from a point to a line segment, ignoring the z coordinates
						float fThisXDistSq = Distance::Point_Lineseg2DSq(Point3d, line, fT);

						//+fZDistSq
						if (fThisXDistSq < fXDistSq)
						{
							// find closest point
							fXDistSq = fThisXDistSq;
							Closest3d = (line.GetPoint(fT));
							//Closest3d.z = Point3d.z;
						}
					}
				}

				// now we have Closest3d being the point on the 2D hull of the shape

				if (m_VSize)
				{
					// negative means from inside to hull
					float fDistToRoof = Point3d.z - (m_VOrigin+m_VSize);		
					float fDistToFloor = m_VOrigin - Point3d.z;

					float fZRoofSq = fDistToRoof*fDistToRoof;
					float fZFloorSq = fDistToFloor*fDistToFloor;

					bool bIsIn2DShape = false;
					if(m_oCachedAreaData.eFlags & eCachedAreaData_PosTypeValid)
						bIsIn2DShape = (m_oCachedAreaData.ePosType == AREA_POS_TYPE_2DINSIDE_ZABOVE)
									|| (m_oCachedAreaData.ePosType == AREA_POS_TYPE_2DINSIDE_ZINSIDE)
									|| (m_oCachedAreaData.ePosType == AREA_POS_TYPE_2DINSIDE_ZBELOW);
					else
						bIsIn2DShape = (CalcCachedPointPosType(Point3d) == AREA_POS_TYPE_2DINSIDE_ZINSIDE);

					if (bIsIn2DShape)
					{
						// point is below, in, or above the area

						if ((Point3d.z < m_VOrigin+m_VSize && Point3d.z > m_VOrigin))
						{
							// Point is inside z-boundary
							if (!m_bObstructRoof && (fZRoofSq < fXDistSq) && (fZRoofSq < fZFloorSq))
							{
								// roof is closer than side
								fZDistSq = fZRoofSq;
								Closest3d = Point3d;
								fXDistSq = 0.0f;
							}

							if (!m_bObstructFloor && (fZFloorSq < fXDistSq) && (fZFloorSq < fZRoofSq))
							{
								// floor is closer than side
								fZDistSq = fZFloorSq;
								Closest3d = Point3d;
								fXDistSq = 0.0f;
							}

							// correcting z-axis value
							if (fZRoofSq < fZFloorSq)
								Closest3d.z = Point3d.z - fDistToRoof;
							else
								Closest3d.z = Point3d.z - fDistToFloor;


						}
						else
						{
							// point is above or below area

							if (fabsf(fDistToRoof) < fabsf(fDistToFloor))
							{
								// being above
								if (!m_bObstructRoof)
								{
									// perpendicular point to Roof
									fXDistSq = 0.0f;
									Closest3d = Point3d;
								}
								// correcting z axis value
								Closest3d.z = m_VOrigin + m_VSize;
								fZDistSq = fZRoofSq;
							}
							else
							{
								// being below
								if (!m_bObstructFloor)
								{
									// perpendicular point to Floor
									fXDistSq = 0.0f;
									Closest3d = Point3d;
								}
								// correcting z axis value
								Closest3d.z = m_VOrigin;
								fZDistSq = fZFloorSq;
							}
						}
					}
					else
					{
						// outside of 2D Shape, so diagonal or only to the side
						if ((Point3d.z > m_VOrigin+m_VSize || Point3d.z < m_VOrigin))
						{
							// Point is outside z-bondary
							// point is above or below area
							if (fabsf(fDistToRoof) < fabsf(fDistToFloor))
							{
								// being above
								fZDistSq = fZRoofSq;
								Closest3d.z = m_VOrigin+m_VSize;

							}
							else
							{
								// being below
								fZDistSq = fZFloorSq;
								Closest3d.z = m_VOrigin;
							}
						}
						else
						{
							// on the side and outside of the area, so point is on the face
							// correct z-Value
							Closest3d.z = Point3d.z;
						}

					}
				}
				else
				{
					// infinite high area
					// Closest is on an edge, ZDistance is 0, so nothing really to do here
				}

				fClosestDistance					= fXDistSq + fZDistSq;
				OnHull3d									= Closest3d;

				// Set the flags
				if (bCacheResult)
					m_oCachedAreaData.eFlags	= (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistNearSqValid | eCachedAreaData_DistNearSqValidNotObstructed);
			}
			else
			{
				CalcClosestPointToObstructedShape(OnHull3d, fClosestDistance, Point3d);

				// Set the flags
				if (bCacheResult)
					m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistNearSqValid);
			}
	
			// Put the shortest distance and point into the data cache
			if (bCacheResult)
			{
				m_oCachedAreaData.v3Pos						= OnHull3d;
				m_oCachedAreaData.fDistanceNearSq	= fClosestDistance;
			}
			break;
		}
	case	 ENTITY_AREA_TYPE_SPHERE:
		{
			Vec3 Temp = Point3d - m_SphereCenter;
			OnHull3d = Temp.normalize() * m_SphereRadius;
			OnHull3d += m_SphereCenter;
			fClosestDistance = OnHull3d.GetSquaredDistance(Point3d);

			// Set the flags and put the shortest distance and point into the data cache
			if (bCacheResult)
			{
				if (bIgnoreSoundObstruction)
					m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistNearSqValid | eCachedAreaData_DistNearSqValidNotObstructed);
				else
					m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistNearSqValid);

				m_oCachedAreaData.v3Pos						= OnHull3d;
				m_oCachedAreaData.fDistanceNearSq	= fClosestDistance;
			}

			return fClosestDistance;

			break;
		}
	case	 ENTITY_AREA_TYPE_BOX:
		{
			if(bIgnoreSoundObstruction)
			{
				Vec3 p3d = m_InvMatrix.TransformPoint(Point3d);

				//Matrix33 back = m_InvMatrix.Invert();
				AABB myAABB(m_BoxMin, m_BoxMax);
				//OBB myOBB = OBB::CreateOBBfromAABB(m_InvMatrix.Invert(), myAABB);

				fClosestDistance = Distance::Point_AABBSq(p3d, myAABB, OnHull3d);
				
				if (m_abBoxSideObstruction[4].bObstructed && OnHull3d.z == myAABB.max.z)
				{
					// Point is on the roof plane, but may be on the edge already
					Vec2 vTop(OnHull3d.x, myAABB.max.y);
					Vec2 vLeft(myAABB.min.x, OnHull3d.y);
					Vec2 vLow(OnHull3d.x, myAABB.min.y);
					Vec2 vRight(myAABB.max.x, OnHull3d.y);

					float fDistanceToTop		= p3d.GetSquaredDistance2D(vTop);
					float fDistanceToLeft		= p3d.GetSquaredDistance2D(vLeft);
					float fDistanceToLow		= p3d.GetSquaredDistance2D(vLow);
					float fDistanceToRight	= p3d.GetSquaredDistance2D(vRight);
					float fTempMinDistance = fDistanceToTop;

					OnHull3d = vTop;

					if (fDistanceToLeft < fTempMinDistance)
					{
						OnHull3d = vLeft;
						fTempMinDistance = fDistanceToLeft;
					}

					if (fDistanceToLow < fTempMinDistance)
					{
						OnHull3d = vLow;
						fTempMinDistance = fDistanceToLow;
					}

					if (fDistanceToRight < fTempMinDistance)
					{
						OnHull3d = vRight;
						fTempMinDistance = fDistanceToRight;
					}

					OnHull3d.z = min(myAABB.max.z, p3d.z);
					fClosestDistance = OnHull3d.GetSquaredDistance(p3d);

				}

				if (m_abBoxSideObstruction[5].bObstructed && OnHull3d.z == myAABB.min.z)
				{
					// Point is on the roof plane, but may be on the edge already
					Vec2 vTop(OnHull3d.x, myAABB.max.y);
					Vec2 vLeft(myAABB.min.x, OnHull3d.y);
					Vec2 vLow(OnHull3d.x, myAABB.min.y);
					Vec2 vRight(myAABB.max.x, OnHull3d.y);

					float fDistanceToTop		= p3d.GetSquaredDistance2D(vTop);
					float fDistanceToLeft		= p3d.GetSquaredDistance2D(vLeft);
					float fDistanceToLow		= p3d.GetSquaredDistance2D(vLow);
					float fDistanceToRight	= p3d.GetSquaredDistance2D(vRight);
					float fTempMinDistance = fDistanceToTop;

					OnHull3d = vTop;

					if (fDistanceToLeft < fTempMinDistance)
					{
						OnHull3d = vLeft;
						fTempMinDistance = fDistanceToLeft;
					}

					if (fDistanceToLow < fTempMinDistance)
					{
						OnHull3d = vLow;
						fTempMinDistance = fDistanceToLow;
					}

					if (fDistanceToRight < fTempMinDistance)
					{
						OnHull3d = vRight;
						fTempMinDistance = fDistanceToRight;
					}

					OnHull3d.z = max(myAABB.min.z, p3d.z);
					fClosestDistance = OnHull3d.GetSquaredDistance(p3d);
				}

				OnHull3d = m_WorldTM.TransformPoint(OnHull3d);

				// Set the flags
				if (bCacheResult)
					m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistNearSqValid | eCachedAreaData_DistNearSqValidNotObstructed);
			}
			else
			{
				CalcClosestPointToObstructedBox(OnHull3d, fClosestDistance, Point3d);

				// Set the flags
				if (bCacheResult)
					m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistNearSqValid);
			}

			// Put the shortest distance and point into the data cache
			if (bCacheResult)
			{
				m_oCachedAreaData.v3Pos						= OnHull3d;
				m_oCachedAreaData.fDistanceNearSq	= fClosestDistance;
			}

			break;
		}
	}

	return fClosestDistance;

}

//////////////////////////////////////////////////////////////////////////
float CArea::CalcPointNearDistSq(const Vec3 &Point3d, bool bIgnoreHeight /* = false */, bool const bIgnoreSoundObstruction /* = true */, bool const bCacheResult /* = true */)
{
	if (!m_bInitialized)
		return -1.0f;

	float fClosestDistance = FLT_MAX;

	switch(m_AreaType)
	{
	case	 ENTITY_AREA_TYPE_SHAPE:
		{
			if(bIgnoreSoundObstruction)
			{
				float fZDistSq = 0.0f;
				float fXDistSq = FLT_MAX;

				for(std::vector<a2DSegment*>::const_iterator it = m_vpSegments.begin(), itEnd = m_vpSegments.end(); it != itEnd; ++ it)
				{
					a2DSegment* curSg = *it;
					a2DPoint curSgStart = curSg->GetStart(), curSgEnd = curSg->GetEnd();

					Lineseg line;
					line.start.x = curSgStart.x;
					line.start.y = curSgStart.y;
					line.end.x = curSgEnd.x;
					line.end.y = curSgEnd.y;

					/// Returns distance from a point to a line segment, ignoring the z coordinates
					float fThisXDistSq = Distance::Point_Lineseg2DSq(Point3d, line);
					fXDistSq = min(fXDistSq, fThisXDistSq);
				}

				if (m_VSize)
				{
					// negative means from inside to hull
					float fDistToRoof = Point3d.z - (m_VOrigin+m_VSize);		
					float fDistToFloor = m_VOrigin - Point3d.z;

					float fZRoofSq = fDistToRoof*fDistToRoof;
					float fZFloorSq = fDistToFloor*fDistToFloor;

					bool bIsIn2DShape = false;
					if(m_oCachedAreaData.eFlags & eCachedAreaData_PosTypeValid)
						bIsIn2DShape = (m_oCachedAreaData.ePosType == AREA_POS_TYPE_2DINSIDE_ZABOVE)
									|| (m_oCachedAreaData.ePosType == AREA_POS_TYPE_2DINSIDE_ZINSIDE)
									|| (m_oCachedAreaData.ePosType == AREA_POS_TYPE_2DINSIDE_ZBELOW);
					else
						bIsIn2DShape = (CalcCachedPointPosType(Point3d) == AREA_POS_TYPE_2DINSIDE_ZINSIDE);

					if (bIsIn2DShape)
					{
						// point is below, in, or above the area
						if ((Point3d.z < m_VOrigin+m_VSize && Point3d.z > m_VOrigin))
						{
							// Point is inside z-boundary
							if (!m_bObstructRoof && (fZRoofSq < fXDistSq) && (fZRoofSq < fZFloorSq))
							{
								// roof is closer than side
								fZDistSq = fZRoofSq;
								fXDistSq = 0.0f;
							}

							if (!m_bObstructFloor && (fZFloorSq < fXDistSq) && (fZFloorSq < fZRoofSq))
							{
								// floor is closer than side
								fZDistSq = fZFloorSq;
								fXDistSq = 0.0f;
							}
						}
						else
						{
							// point is above or below area
							if (fabsf(fDistToRoof) < fabsf(fDistToFloor))
							{
								// being above
								fZDistSq = fZRoofSq;

								if (!m_bObstructRoof)
								{
									// perpendicular point to Roof
									fXDistSq = 0.0f;
								}
							}
							else
							{
								// being below
								fZDistSq = fZFloorSq;

								if (!m_bObstructFloor)
								{
									// perpendicular point to Floor
									fXDistSq = 0.0f;
								}
							}
						}
					}
					else
					{
						// outside of 2D Shape, so diagonal or only to the side
						if ((Point3d.z < m_VOrigin+m_VSize && Point3d.z > m_VOrigin))
						{
							// Point is inside z-boundary
						}
						else
						{
							// point is above or below area
							if (fabsf(fDistToRoof) < fabsf(fDistToFloor))
							{
								// being above
								fZDistSq = fZRoofSq;
							}
							else
							{
								// being below
								fZDistSq = fZFloorSq;
							}
						}
					}
				}
				else
				{
					// infinite high area
					// Closest is on an edge, ZDistance is 0, so nothing really to do here
				}

				fClosestDistance = fXDistSq;

				if (!bIgnoreHeight)
					fClosestDistance += fZDistSq;

				// Set the flags and put the shortest distance into the data cache
				if (bCacheResult)
				{
					if (bIgnoreHeight)
						m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistNearSqValid | eCachedAreaData_DistNearSqValidNotObstructed | eCachedAreaData_DistNearSqValidHeightIgnored);
					else
						m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistNearSqValid | eCachedAreaData_DistNearSqValidNotObstructed);

					m_oCachedAreaData.fDistanceNearSq	= fClosestDistance;
				}
			}
			else
			{
				Vec3 v3NotUsed;
				CalcClosestPointToObstructedShape(v3NotUsed, fClosestDistance, Point3d);

				// Set the flags
				if (bCacheResult)
				{
					if (bIgnoreHeight)
						m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistNearSqValid | eCachedAreaData_DistNearSqValidHeightIgnored);
					else
						m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistNearSqValid);

					m_oCachedAreaData.fDistanceNearSq	= fClosestDistance;
				}
			}

			break;
		}

	case	 ENTITY_AREA_TYPE_SPHERE:
		{
			float fLength = 0;
			Vec3 vTemp(Point3d);

			if (bIgnoreHeight)
				vTemp.z = m_SphereCenter.z;

			fLength = m_SphereCenter.GetDistance(vTemp) - m_SphereRadius;

			// Set the flags and put the shortest distance into the data cache
			if (bCacheResult)
			{
				if (bIgnoreHeight)
					m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistNearSqValid | eCachedAreaData_DistNearSqValidHeightIgnored);
				else
					m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistNearSqValid);

				m_oCachedAreaData.fDistanceNearSq	= fLength * fLength;
			}
			else
				return fLength * fLength;

			return m_oCachedAreaData.fDistanceNearSq;
		}
	case	 ENTITY_AREA_TYPE_BOX:
		{
			if(bIgnoreSoundObstruction)
			{
				Vec3 p3d = m_InvMatrix.TransformPoint(Point3d);
				Vec3 OnHull3d;

				if (bIgnoreHeight)
					p3d.z = m_BoxMin.z;

				AABB myAABB(m_BoxMin, m_BoxMax);

				fClosestDistance = Distance::Point_AABBSq(p3d, myAABB, OnHull3d);

				if (m_abBoxSideObstruction[4].bObstructed && OnHull3d.z == myAABB.max.z)
				{
					// Point is on the roof plane, but may be on the edge already
					Vec2 vTop(OnHull3d.x, myAABB.max.y);
					Vec2 vLeft(myAABB.min.x, OnHull3d.y);
					Vec2 vLow(OnHull3d.x, myAABB.min.y);
					Vec2 vRight(myAABB.max.x, OnHull3d.y);

					float fDistanceToTop		= p3d.GetSquaredDistance2D(vTop);
					float fDistanceToLeft		= p3d.GetSquaredDistance2D(vLeft);
					float fDistanceToLow		= p3d.GetSquaredDistance2D(vLow);
					float fDistanceToRight	= p3d.GetSquaredDistance2D(vRight);
					float fTempMinDistance = fDistanceToTop;

					OnHull3d = vTop;

					if (fDistanceToLeft < fTempMinDistance)
					{
						OnHull3d = vLeft;
						fTempMinDistance = fDistanceToLeft;
					}

					if (fDistanceToLow < fTempMinDistance)
					{
						OnHull3d = vLow;
						fTempMinDistance = fDistanceToLow;
					}

					if (fDistanceToRight < fTempMinDistance)
					{
						OnHull3d = vRight;
						fTempMinDistance = fDistanceToRight;
					}

					OnHull3d.z = min(myAABB.max.z, p3d.z);
					fClosestDistance = OnHull3d.GetSquaredDistance(p3d);

				}

				if (m_abBoxSideObstruction[5].bObstructed && OnHull3d.z == myAABB.min.z)
				{
					// Point is on the floor plane, but may be on the edge already
					Vec2 vTop(OnHull3d.x, myAABB.max.y);
					Vec2 vLeft(myAABB.min.x, OnHull3d.y);
					Vec2 vLow(OnHull3d.x, myAABB.min.y);
					Vec2 vRight(myAABB.max.x, OnHull3d.y);

					float fDistanceToTop		= p3d.GetSquaredDistance2D(vTop);
					float fDistanceToLeft		= p3d.GetSquaredDistance2D(vLeft);
					float fDistanceToLow		= p3d.GetSquaredDistance2D(vLow);
					float fDistanceToRight	= p3d.GetSquaredDistance2D(vRight);
					float fTempMinDistance = fDistanceToTop;

					OnHull3d = vTop;

					if (fDistanceToLeft < fTempMinDistance)
					{
						OnHull3d = vLeft;
						fTempMinDistance = fDistanceToLeft;
					}

					if (fDistanceToLow < fTempMinDistance)
					{
						OnHull3d = vLow;
						fTempMinDistance = fDistanceToLow;
					}

					if (fDistanceToRight < fTempMinDistance)
					{
						OnHull3d = vRight;
						fTempMinDistance = fDistanceToRight;
					}

					OnHull3d.z = max(myAABB.min.z, p3d.z);
					fClosestDistance = OnHull3d.GetSquaredDistance(p3d);
				}

				// Set the flags
				if (bCacheResult)
				{
					if (bIgnoreHeight)
						m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistNearSqValid | eCachedAreaData_DistNearSqValidNotObstructed | eCachedAreaData_DistNearSqValidHeightIgnored);
					else
						m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistNearSqValid | eCachedAreaData_DistNearSqValidNotObstructed);
				}
			}
			else
			{
				Vec3 v3NotUsed;
				CalcClosestPointToObstructedBox(v3NotUsed, fClosestDistance, Point3d);

				// Set the flags
				if (bCacheResult)
				{
					if (bIgnoreHeight)
						m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistNearSqValid | eCachedAreaData_DistNearSqValidHeightIgnored);
					else
						m_oCachedAreaData.eFlags = (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_DistNearSqValid);
				}
			}

			// Put the shortest distance into the data cache
			if (bCacheResult)
				m_oCachedAreaData.fDistanceNearSq	= fClosestDistance;

			// TODO transform point back to world
			break;
		}
	}

	return fClosestDistance;
}

//////////////////////////////////////////////////////////////////////////
EAreaPosType const CArea::CalcCachedPointPosType(Vec3 const& rv3Pos, bool const bCacheResult /* = true */)
{
	EAreaPosType eTempPosType	= AREA_POS_TYPE_COUNT;
	
	bool const bIsIn2DShape = CalcPointWithin(rv3Pos, true);

	switch(m_AreaType)
	{
	case ENTITY_AREA_TYPE_SHAPE:
		{
			if(m_VSize > 0.0f)
			{
				// Negative means from inside to hull
				float const fDistToRoof		= rv3Pos.z - (m_VOrigin+m_VSize);		
				float const fDistToFloor	= m_VOrigin - rv3Pos.z;

				if(bIsIn2DShape)
				{
					// Point is below, in, or above the area
					if((rv3Pos.z < m_VOrigin+m_VSize && rv3Pos.z > m_VOrigin))
						eTempPosType = AREA_POS_TYPE_2DINSIDE_ZINSIDE;
					else
					{
						// Point is above or below area
						if(fabsf(fDistToRoof) < fabsf(fDistToFloor))
							eTempPosType = AREA_POS_TYPE_2DINSIDE_ZABOVE;
						else
							eTempPosType = AREA_POS_TYPE_2DINSIDE_ZBELOW;
					}
				}
				else
				{
					// Outside of 2D Shape, so diagonal or only to the side
					if((rv3Pos.z > m_VOrigin+m_VSize || rv3Pos.z < m_VOrigin))
					{
						// Point is outside z-boundary
						// point is above or below area
						if(fabsf(fDistToRoof) < fabsf(fDistToFloor))
							eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZABOVE;
						else
							eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZBELOW;
					}
					else
						eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZINSIDE;
				}
			}
			else
			{
				// No height means infinite Z boundaries (no roof, no floor)
				if(bIsIn2DShape)
					eTempPosType = AREA_POS_TYPE_2DINSIDE_ZINSIDE;
				else
					eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZINSIDE;
			}

			if (bCacheResult)
			{
				m_oCachedAreaData.eFlags		= (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosTypeValid);
				m_oCachedAreaData.ePosType	= eTempPosType;
			}
		}
		break;
	case ENTITY_AREA_TYPE_BOX:
		{
			if(m_BoxMax.z > 0.0f)
			{
				Vec3 const ov3PosLocalSpace(m_InvMatrix.TransformPoint(rv3Pos));
				if(bIsIn2DShape)
				{
					if(ov3PosLocalSpace.z < 0.0f)
						eTempPosType = AREA_POS_TYPE_2DINSIDE_ZBELOW;
					else if(ov3PosLocalSpace.z > m_BoxMax.z)
						eTempPosType = AREA_POS_TYPE_2DINSIDE_ZABOVE;
					else
						eTempPosType = AREA_POS_TYPE_2DINSIDE_ZINSIDE;
				}
				else
				{
					if(ov3PosLocalSpace.z < 0.0f)
						eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZBELOW;
					else if(ov3PosLocalSpace.z > m_BoxMax.z)
						eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZABOVE;
					else
						eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZINSIDE;
				}
			}
			else
			{
				// No height means infinite Z boundaries (no roof, no floor)
				if(bIsIn2DShape)
					eTempPosType = AREA_POS_TYPE_2DINSIDE_ZINSIDE;
				else
					eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZINSIDE;
			}				

			if (bCacheResult)
			{
				m_oCachedAreaData.eFlags		= (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosTypeValid);
				m_oCachedAreaData.ePosType	= eTempPosType;
			}
		}
		break;
	case ENTITY_AREA_TYPE_SPHERE:
		{
			float const fSphereMostTop		= m_SphereCenter.z + m_SphereRadius;
			float const fSphereMostBottom	= m_SphereCenter.z - m_SphereRadius;
			bool const bAbove							= rv3Pos.z > fSphereMostTop;
			bool const bBelow							= rv3Pos.z < fSphereMostBottom;
			bool const bIsIn3DShape				= CalcPointWithin(rv3Pos, false);
			
			if(bIsIn3DShape)
				eTempPosType = AREA_POS_TYPE_2DINSIDE_ZINSIDE;
			else
			{
				if(bIsIn2DShape)
				{
					// We're inside of the sphere 2D silhouette but not inside the sphere itself
					// now check if we're above its max z or below its min z
					if(bAbove)
						eTempPosType = AREA_POS_TYPE_2DINSIDE_ZABOVE;

					if(bBelow)
						eTempPosType = AREA_POS_TYPE_2DINSIDE_ZBELOW;					
				}
				else
				{
					// We're outside of the sphere 2D silhouette and outside of the sphere itself
					// now check if we're above its max z or below its min z
					eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZINSIDE;

					if(bAbove)
						eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZABOVE;

					if(bBelow)
						eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZBELOW;
				}
			}

			if (bCacheResult)
			{
				m_oCachedAreaData.eFlags		= (ECachedAreaData)(m_oCachedAreaData.eFlags | eCachedAreaData_PosTypeValid);
				m_oCachedAreaData.ePosType	= eTempPosType;
			}
		}
		break;
	}

	return eTempPosType;
}

//////////////////////////////////////////////////////////////////////////
void CArea::CalcClosestPointToObstructedShape(Vec3& outClosest, float& outClosestDistSq, Vec3 const& sourcePos)const
{
	const size_t nSegmentCount	= m_vpSegments.size();
	Lineseg oLine;

	Vec3 closest(ZERO);
	float closestDistSq(FLT_MAX);

	// If this area got a height
	if(m_VSize)
	{
		float const fRoofWorldPosZ = m_VOrigin+m_VSize;

		// Find the closest point
		// First of all check if we're either right above a non-obstructing roof or below a non-obstructing floor
		// if so, just use this data since we have the shortest distance right there
		if(m_oCachedAreaData.ePosType == AREA_POS_TYPE_2DINSIDE_ZABOVE && !m_bObstructRoof)
		{
			closestDistSq = (sourcePos.z - fRoofWorldPosZ)*(sourcePos.z - fRoofWorldPosZ);
			closest.x	= sourcePos.x;
			closest.y	= sourcePos.y;
			closest.z	= fRoofWorldPosZ;
		}
		else if(m_oCachedAreaData.ePosType == AREA_POS_TYPE_2DINSIDE_ZBELOW && !m_bObstructFloor)
		{
			closestDistSq = (m_VOrigin - sourcePos.z)*(m_VOrigin - sourcePos.z);
			closest.x	= sourcePos.x;
			closest.y	= sourcePos.y;
			closest.z	= m_VOrigin;
		}
		else
		{
			PrefetchLine(&m_vpSegments[0], 0);

			for(unsigned int nIdx=0; nIdx < nSegmentCount; ++nIdx)
			{
				a2DSegment* const curSg				= m_vpSegments[nIdx];
				float const fCurrSegStart[2]	= {curSg->GetStart().x, curSg->GetStart().y};
				float const fCurrSegEnd[2]		= {curSg->GetEnd().x, curSg->GetEnd().y};

				// If the segment is not obstructed get the closest point to it
				if(!curSg->bObstructSound)
				{
					float fPosZ			= sourcePos.z;
					bool bAdjusted	= false;

					// Adjust Z position if we're outside the boundaries
					if(fPosZ > fRoofWorldPosZ)
					{
						fPosZ			= fRoofWorldPosZ;
						bAdjusted	= true;
					}
					else if(fPosZ < m_VOrigin)
					{
						fPosZ			= m_VOrigin;
						bAdjusted	= true;
					}

					oLine.start	= Vec3(fCurrSegStart[0], fCurrSegStart[1], fPosZ);
					oLine.end		= Vec3(fCurrSegEnd[0], fCurrSegEnd[1], fPosZ);

					// If we're outside the Z boundaries we need to include Z on our test
					float fT;
					float fTempDistToLineSq = bAdjusted ? 
						Distance::Point_LinesegSq(sourcePos, oLine, fT) : 
						Distance::Point_Lineseg2DSq(sourcePos, oLine, fT);

					if(fTempDistToLineSq < closestDistSq)
					{
						closestDistSq = fTempDistToLineSq;
						closest		= oLine.GetPoint(fT);
					}
				}
				else
				{
					// Otherwise we need to check the roof and the floor if we're not inside the area
					if(m_oCachedAreaData.ePosType != AREA_POS_TYPE_2DINSIDE_ZINSIDE)
					{
						// Roof
						if(!m_bObstructRoof)
						{
							oLine.start				= Vec3(fCurrSegStart[0], fCurrSegStart[1], fRoofWorldPosZ);
							oLine.end					= Vec3(fCurrSegEnd[0], fCurrSegEnd[1], fRoofWorldPosZ);
							float fT;
							float fTempDistToLineSq	= Distance::Point_LinesegSq(sourcePos, oLine, fT);
							
							if(fTempDistToLineSq < closestDistSq)
							{
								closestDistSq = fTempDistToLineSq;
								closest		= oLine.GetPoint(fT);
							}
						}

						// Floor
						if(!m_bObstructFloor)
						{
							oLine.start				= Vec3(fCurrSegStart[0], fCurrSegStart[1], m_VOrigin);
							oLine.end					= Vec3(fCurrSegEnd[0], fCurrSegEnd[1], m_VOrigin);

							float fT;
							float fTempDistToLineSq	= Distance::Point_LinesegSq(sourcePos, oLine, fT);
							
							if(fTempDistToLineSq < closestDistSq)
							{
								closestDistSq = fTempDistToLineSq;
								closest		= oLine.GetPoint(fT);
							}
						}
					}					
				}
			}
		}

		// If we're inside an area we always need to check a non-obstructing roof and floor
		// this needs to be done only once right after the loop
		if(m_oCachedAreaData.ePosType == AREA_POS_TYPE_2DINSIDE_ZINSIDE)
		{
			// Roof
			if(!m_bObstructRoof)
			{
				float fTempDistToLineSq	= (sourcePos.z - fRoofWorldPosZ)*(sourcePos.z - fRoofWorldPosZ);

				if(fTempDistToLineSq < closestDistSq)
				{
					closestDistSq = fTempDistToLineSq;
					closest.x	= sourcePos.x;
					closest.y	= sourcePos.y;
					closest.z	= fRoofWorldPosZ;
				}
			}

			// Floor
			if(!m_bObstructFloor)
			{
				float fTempDistToLineSq	= (m_VOrigin - sourcePos.z)*(m_VOrigin - sourcePos.z);

				if(fTempDistToLineSq < closestDistSq)
				{
					closestDistSq = fTempDistToLineSq;
					closest.x	= sourcePos.x;
					closest.y	= sourcePos.y;
					closest.z	= m_VOrigin;
				}
			}
		}
	}
	else // This area has got infinite height
	{
		// Find the closest distance to non-obstructing segments, at source pos height
		for(unsigned int nIdx=0; nIdx < nSegmentCount; ++nIdx)
		{
			a2DSegment* const curSg				= m_vpSegments[nIdx];
			float const fCurrSegStart[2]	= {curSg->GetStart().x, curSg->GetStart().y};
			float const fCurrSegEnd[2]		= {curSg->GetEnd().x, curSg->GetEnd().y};

			// If the segment is not obstructed get the closest point and continue
			if(!curSg->bObstructSound)
			{
				oLine.start				= Vec3(fCurrSegStart[0], fCurrSegStart[1], sourcePos.z);
				oLine.end					= Vec3(fCurrSegEnd[0], fCurrSegEnd[1], sourcePos.z);
				
				float fT;
				float fTempDistToLineSq	= Distance::Point_Lineseg2DSq(sourcePos, oLine, fT); // Ignore Z

				if(fTempDistToLineSq < closestDistSq)
				{
					closestDistSq = fTempDistToLineSq;
					closest		= oLine.GetPoint(fT);
				}
			}
		}
	}

	// If there was no calculation, most likely all sides + roof + floor are obstructed, return the source position
	if(closest.IsZeroFast() && closestDistSq == FLT_MAX)
		closest = sourcePos;

	outClosest = closest;
	outClosestDistSq = closestDistSq;
}

//////////////////////////////////////////////////////////////////////////
void CArea::CalcClosestPointToObstructedBox(Vec3& outClosest, float& outClosestDistSq, Vec3 const& sourcePos) const
{
	Vec3 source(sourcePos);
	Vec3 closest(sourcePos);
	float closestDistSq	= FLT_MAX;

	if (!m_bAllObstructed)
	{
		Vec3 v3PosLocalSpace(m_InvMatrix.TransformPoint(source));
		Vec3 v3BoxMin;
		Vec3 v3BoxMax;

		// Only manipulate Z so it fits X and Y layout (center of box)
		v3BoxMin.x = m_BoxMin.x;
		v3BoxMin.y = m_BoxMin.y;
		v3BoxMin.z = m_BoxMax.z * -0.5f;

		v3BoxMax.x = m_BoxMax.x;
		v3BoxMax.y = m_BoxMax.y;
		v3BoxMax.z = m_BoxMax.z * 0.5f;

		// Also adjust local space source position Z
		v3PosLocalSpace.z -= v3BoxMax.z;

		// Determine the closest side and check for obstruction
		int nClosestSideIdx								= -1;
		bool bAllObstructedExceptBackside = false;
		Vec3 aoBoxBackSideEdges[4];

		if (v3PosLocalSpace.x > v3BoxMax.x)
		{
			// Side 2 is closest, 4 is backside
			nClosestSideIdx	= 1;

			if (m_nObstructedCount == 5 && !m_abBoxSideObstruction[3].bObstructed)
			{
				// Describe possible snapping positions on back side 4
				aoBoxBackSideEdges[0] = Vec3(v3BoxMin.x, v3BoxMin.y, v3PosLocalSpace.z);
				aoBoxBackSideEdges[1] = Vec3(v3BoxMin.x, v3PosLocalSpace.y, v3BoxMax.z);
				aoBoxBackSideEdges[2] = Vec3(v3BoxMin.x, v3BoxMax.y, v3PosLocalSpace.z);
				aoBoxBackSideEdges[3] = Vec3(v3BoxMin.x, v3PosLocalSpace.y, v3BoxMin.z);

				bAllObstructedExceptBackside = true;
			}
		}
		else if (v3PosLocalSpace.x < v3BoxMin.x)
		{
			// Side 4 is closest, 2 is backside
			nClosestSideIdx	= 3;

			if (m_nObstructedCount == 5 && !m_abBoxSideObstruction[1].bObstructed)
			{
				// Describe possible snapping positions on back side 2
				aoBoxBackSideEdges[0] = Vec3(v3BoxMax.x, v3BoxMax.y, v3PosLocalSpace.z);
				aoBoxBackSideEdges[1] = Vec3(v3BoxMax.x, v3PosLocalSpace.y, v3BoxMax.z);
				aoBoxBackSideEdges[2] = Vec3(v3BoxMax.x, v3BoxMin.y, v3PosLocalSpace.z);
				aoBoxBackSideEdges[3] = Vec3(v3BoxMax.x, v3PosLocalSpace.y, v3BoxMin.z);

				bAllObstructedExceptBackside = true;
			}
		}
		else
		{
			if (v3PosLocalSpace.y < v3BoxMin.y)
			{
				// Side 0 is closest, 2 is backside
				nClosestSideIdx	= 0;

				if (m_nObstructedCount == 5 && !m_abBoxSideObstruction[2].bObstructed)
				{
					// Describe possible snapping positions on back side 2
					aoBoxBackSideEdges[0] = Vec3(v3BoxMin.x, v3BoxMax.y, v3PosLocalSpace.z);
					aoBoxBackSideEdges[1] = Vec3(v3PosLocalSpace.x, v3BoxMax.y, v3BoxMax.z);
					aoBoxBackSideEdges[2] = Vec3(v3BoxMax.x, v3BoxMax.y, v3PosLocalSpace.z);
					aoBoxBackSideEdges[3] = Vec3(v3PosLocalSpace.x, v3BoxMax.y, v3BoxMin.z);

					bAllObstructedExceptBackside = true;
				}
			}
			else if (v3PosLocalSpace.y > v3BoxMax.y)
			{
				// Side 2 is closest, 0 is backside
				nClosestSideIdx	= 2;

				if (m_nObstructedCount == 5 && !m_abBoxSideObstruction[0].bObstructed)
				{
					// Describe possible snapping positions on back side 0
					aoBoxBackSideEdges[0] = Vec3(v3BoxMax.x, v3BoxMin.y, v3PosLocalSpace.z);
					aoBoxBackSideEdges[1] = Vec3(v3PosLocalSpace.x, v3BoxMin.y, v3BoxMax.z);
					aoBoxBackSideEdges[2] = Vec3(v3BoxMin.x, v3BoxMin.y, v3PosLocalSpace.z);
					aoBoxBackSideEdges[3] = Vec3(v3PosLocalSpace.x, v3BoxMin.y, v3BoxMin.z);

					bAllObstructedExceptBackside = true;
				}
			}
			else
			{
				if (v3PosLocalSpace.z < v3BoxMin.z)
				{
					// Side 5 is closest, 4 is backside
					nClosestSideIdx	= 5;

					if (m_nObstructedCount == 5 && !m_abBoxSideObstruction[4].bObstructed)
					{
						// Describe possible snapping positions on back side 4
						aoBoxBackSideEdges[0] = Vec3(v3PosLocalSpace.x, v3BoxMin.y, v3BoxMax.z);
						aoBoxBackSideEdges[1] = Vec3(v3BoxMin.x, v3PosLocalSpace.y, v3BoxMax.z);
						aoBoxBackSideEdges[2] = Vec3(v3PosLocalSpace.x, v3BoxMax.y, v3BoxMax.z);
						aoBoxBackSideEdges[3] = Vec3(v3BoxMax.x, v3PosLocalSpace.y, v3BoxMax.z);

						bAllObstructedExceptBackside = true;
					}
				}
				else if (v3PosLocalSpace.z > v3BoxMax.z)
				{
					// Side 4 is closest, 5 is backside
					nClosestSideIdx	= 4;

					if (m_nObstructedCount == 5 && !m_abBoxSideObstruction[5].bObstructed)
					{
						// Describe possible snapping positions on back side 5
						aoBoxBackSideEdges[0] = Vec3(v3PosLocalSpace.x, v3BoxMin.y, v3BoxMin.z);
						aoBoxBackSideEdges[1] = Vec3(v3BoxMin.x, v3PosLocalSpace.y, v3BoxMin.z);
						aoBoxBackSideEdges[2] = Vec3(v3PosLocalSpace.x, v3BoxMax.y, v3BoxMin.z);
						aoBoxBackSideEdges[3] = Vec3(v3BoxMax.x, v3PosLocalSpace.y, v3BoxMin.z);

						bAllObstructedExceptBackside = true;
					}
				}
				else
				{
					// We're inside the box
					float const fDistSide[6] = 
					{
						abs(m_abBoxSideObstruction[0].bObstructed ? FLT_MAX : v3BoxMin.y - v3PosLocalSpace.y),
						abs(m_abBoxSideObstruction[1].bObstructed ? FLT_MAX : v3BoxMax.x - v3PosLocalSpace.x),
						abs(m_abBoxSideObstruction[2].bObstructed ? FLT_MAX : v3BoxMax.y - v3PosLocalSpace.y),
						abs(m_abBoxSideObstruction[3].bObstructed ? FLT_MAX : v3BoxMin.x - v3PosLocalSpace.x),
						abs(m_abBoxSideObstruction[4].bObstructed ? FLT_MAX : v3BoxMax.z - v3PosLocalSpace.z),
						abs(m_abBoxSideObstruction[5].bObstructed ? FLT_MAX : v3BoxMin.z - v3PosLocalSpace.z)
					};

					float const fClosestDist = min(min(min(fDistSide[0], fDistSide[1]), min(fDistSide[2], fDistSide[3])), min(fDistSide[4], fDistSide[5]));
					for (unsigned int i = 0; i < 6; ++i)
					{
						if (fDistSide[i] == fClosestDist)
						{
							switch (i)
							{
							case 0:
								closest = Vec3(v3PosLocalSpace.x, v3PosLocalSpace.y - fClosestDist, v3PosLocalSpace.z);
								break;
							case 1:
								closest = Vec3(v3PosLocalSpace.x + fClosestDist, v3PosLocalSpace.y, v3PosLocalSpace.z);
								break;
							case 2:
								closest = Vec3(v3PosLocalSpace.x, v3PosLocalSpace.y + fClosestDist, v3PosLocalSpace.z);
								break;
							case 3:
								closest = Vec3(v3PosLocalSpace.x - fClosestDist, v3PosLocalSpace.y, v3PosLocalSpace.z);
								break;
							case 4:
								closest = Vec3(v3PosLocalSpace.x, v3PosLocalSpace.y, v3PosLocalSpace.z + fClosestDist);
								break;
							case 5:
								closest = Vec3(v3PosLocalSpace.x, v3PosLocalSpace.y, v3PosLocalSpace.z - fClosestDist);
								break;
							}

							// Revert local space source position Z manipulation
							closest.z += v3BoxMax.z;

							// Transform it all back to world coordinates
							outClosest				= m_WorldTM.TransformPoint(closest);
							outClosestDistSq	= Vec3(source - outClosest).GetLengthSquared();
							return;
						}
					}
				}
			}
		}

		// We're done determining the us facing side and we're not inside the box,
		// now check if the side not obstructed and return the closest position on it
		if (nClosestSideIdx > -1 && !m_abBoxSideObstruction[nClosestSideIdx].bObstructed)
		{
			// The shortest side is not obstructed
			closest.x = min(max(v3PosLocalSpace.x, v3BoxMin.x), v3BoxMax.x);
			closest.y = min(max(v3PosLocalSpace.y, v3BoxMin.y), v3BoxMax.y);
			closest.z = min(max(v3PosLocalSpace.z, v3BoxMin.z), v3BoxMax.z);

			// Revert local space source position Z manipulation
			// do this always since Z min is always 0
			closest.z += v3BoxMax.z;

			// Transform the result back to world values
			outClosest				= m_WorldTM.TransformPoint(closest);
			outClosestDistSq	= Vec3(source - outClosest).GetLengthSquared();
			return;
		}

		// If we get here the closest side was obstructed
		// Now describe the 6 sides by applying min and max coordinate values
		SBoxSide const aoBoxSides[6] =
		{
			SBoxSide(v3BoxMin, Vec3(v3BoxMax.x, v3BoxMin.y, v3BoxMax.z)),
			SBoxSide(Vec3(v3BoxMax.x, v3BoxMin.y, v3BoxMin.z), v3BoxMax),
			SBoxSide(Vec3(v3BoxMin.x, v3BoxMax.y, v3BoxMin.z), v3BoxMax),
			SBoxSide(v3BoxMin, Vec3(v3BoxMin.x, v3BoxMax.y, v3BoxMax.z)),
			SBoxSide(Vec3(v3BoxMin.x, v3BoxMin.y, v3BoxMax.z), v3BoxMax),
			SBoxSide(v3BoxMin, Vec3(v3BoxMax.x, v3BoxMax.y, v3BoxMin.z))
		};

		// Check all non-obstructing sides, get the closest position on them
		float fClosestDist = FLT_MAX;
		Vec3 v3ClosestPoint;
		for (unsigned int i = 0; i < 6; ++i)
		{
			if (!m_abBoxSideObstruction[i].bObstructed)
			{
				if (bAllObstructedExceptBackside)
				{
					// At least 2 axis must be within boundaries, which means we're facing directly a side and snapping is necessary
					bool const bWithinX = v3PosLocalSpace.x > v3BoxMin.x && v3PosLocalSpace.x < v3BoxMax.x;
					bool const bWithinY = v3PosLocalSpace.y > v3BoxMin.y && v3PosLocalSpace.y < v3BoxMax.y;
					bool const bWithinZ = v3PosLocalSpace.z > v3BoxMin.z && v3PosLocalSpace.z < v3BoxMax.z;

					if(	bWithinX && (bWithinY || bWithinZ) ||
							bWithinY && (bWithinX || bWithinZ) ||
							bWithinZ && (bWithinX || bWithinY))
					{
						// Get the distance to the edges and choose the shortest one
						float const fDistToEdges[4] = 
						{
							Vec3(v3PosLocalSpace - aoBoxBackSideEdges[0]).GetLengthSquared(),
							Vec3(v3PosLocalSpace - aoBoxBackSideEdges[1]).GetLengthSquared(),
							Vec3(v3PosLocalSpace - aoBoxBackSideEdges[2]).GetLengthSquared(),
							Vec3(v3PosLocalSpace - aoBoxBackSideEdges[3]).GetLengthSquared()
						};

						float const fClosestDistToEdges = min(min(fDistToEdges[0], fDistToEdges[1]), min(fDistToEdges[2], fDistToEdges[3]));
						for (unsigned int j = 0; j < 4; ++j)
						{
							if (fDistToEdges[j] == fClosestDistToEdges)
							{
								// Snap to it
								closest = aoBoxBackSideEdges[j];
								break;
							}
						}

						break;
					}
				}

				v3ClosestPoint.x = min(max(v3PosLocalSpace.x, aoBoxSides[i].v3MinValues.x), aoBoxSides[i].v3MaxValues.x);
				v3ClosestPoint.y = min(max(v3PosLocalSpace.y, aoBoxSides[i].v3MinValues.y), aoBoxSides[i].v3MaxValues.y);
				v3ClosestPoint.z = min(max(v3PosLocalSpace.z, aoBoxSides[i].v3MinValues.z), aoBoxSides[i].v3MaxValues.z);

				float const fTemp = Vec3(v3PosLocalSpace - v3ClosestPoint).GetLengthSquared();
				if (fTemp < fClosestDist)
				{
					fClosestDist	= fTemp;
					closest	= v3ClosestPoint;
				}
			}
		}

		// Revert local space source position Z manipulation
		closest.z += v3BoxMax.z;

		// Transform the result back to world values
		outClosest				= m_WorldTM.TransformPoint(closest);
		outClosestDistSq	= Vec3(source - outClosest).GetLengthSquared();
	}
	else
	{
		outClosest = sourcePos;
	}
}

//////////////////////////////////////////////////////////////////////////
void CArea::InvalidateCachedAreaData()
{
	m_oCachedAreaData.eFlags						= eCachedAreaData_None;
	m_oCachedAreaData.ePosType					= AREA_POS_TYPE_COUNT;
	m_oCachedAreaData.fDistanceWithinSq	= FLT_MAX;
	m_oCachedAreaData.fDistanceNearSq		= FLT_MAX;
	m_oCachedAreaData.bPointWithin			= false;
	m_oCachedAreaData.v3Pos.zero();
}

//////////////////////////////////////////////////////////////////////////
void	CArea::SetPoints( const Vec3* const vPoints, const bool* const pabSoundObstructionSegments, const int nPointsCount )
{
	m_AreaType = ENTITY_AREA_TYPE_SHAPE;
	ClearPoints();
	// at least three points needed to create closed shape
	if (nPointsCount>2)
	{
		m_bInitialized = true;
		float minZ = 10000000.0f;
		//////////////////////////////////////////////////////////////////////////
		for (int i = 0; i < nPointsCount; ++i)
		{
			if (vPoints[i].z < minZ)
				minZ = vPoints[i].z;
		}
		m_VOrigin = minZ;

		int pIdx;
		for(pIdx=1; pIdx<nPointsCount; ++pIdx)
		{
			AddSegment( *((CArea::a2DPoint*)(vPoints+pIdx-1)), *((CArea::a2DPoint*)(vPoints+pIdx)), *(pabSoundObstructionSegments+pIdx-1) );
		}
		AddSegment( *((CArea::a2DPoint*)(vPoints+pIdx-1)), *((CArea::a2DPoint*)(vPoints)), *(pabSoundObstructionSegments+pIdx-1) );
		CalcBBox( );
	}

	m_pAreaManager->SetAreasDirty();
}

//////////////////////////////////////////////////////////////////////////
void	CArea::SetBox( const Vec3& min,const Vec3& max,const Matrix34 &tm )
{
	m_AreaType			= ENTITY_AREA_TYPE_BOX;
	m_bInitialized	= true;
	m_BoxMin				= min;
	m_BoxMax				= max;
	m_InvMatrix			= tm.GetInverted();


/*
	uint32 nErrorCode=0;
	uint32 minValid = min.IsValid();
	if (minValid==0) nErrorCode|=0x8000;
	uint32 maxValid = max.IsValid();
	if (maxValid==0) nErrorCode|=0x8000;

	if (max.x < min.x) nErrorCode|=0x0001;
	if (max.y < min.y) nErrorCode|=0x0001;
	if (max.z < min.z) nErrorCode|=0x0001;
	if (min.x < -8000) nErrorCode|=0x0002;
	if (min.y < -8000) nErrorCode|=0x0004;
	if (min.z < -8000) nErrorCode|=0x0008;
	if (max.x > +8000) nErrorCode|=0x0010;
	if (max.y > +8000) nErrorCode|=0x0020;
	if (max.z > +8000) nErrorCode|=0x0040;
	assert(nErrorCode==0);

	if (nErrorCode)
	{
		CryFatalError("Fatal Error: BBox in EntitySystem is out of range");
		//AnimWarning("CryAnimation: Invalid BBox (%.3f,%.3f,%.3f)-(%.3f,%.3f,%.3f). ModenPath: '%s'  ErrorCode: %08x", 	m_AABB.min.x, m_AABB.min.y, m_AABB.min.z, m_AABB.max.x, m_AABB.max.y, m_AABB.max.z, 	m_pInstance->m_pModel->GetFilePathCStr(), nErrorCode);
		assert(0);
	}
	//if (rAnim.m_nEAnimID>=numAnimations)

*/

	m_pAreaManager->SetAreasDirty();
}

//////////////////////////////////////////////////////////////////////////
void CArea::SetMatrix( const Matrix34 &tm )
{
	m_InvMatrix = tm.GetInverted();
	m_WorldTM = tm;

	m_pAreaManager->SetAreasDirty();
}

//////////////////////////////////////////////////////////////////////////
void CArea::GetMatrix( Matrix34& tm ) const
{
	tm = m_WorldTM;
}

//////////////////////////////////////////////////////////////////////////
void	CArea::SetSphere( const Vec3& center,float fRadius )
{
	m_bInitialized = true;
	m_AreaType = ENTITY_AREA_TYPE_SPHERE;
	m_SphereCenter = center;
	m_SphereRadius = fRadius;
	m_SphereRadius2 = m_SphereRadius*m_SphereRadius;

	m_pAreaManager->SetAreasDirty();
}

//////////////////////////////////////////////////////////////////////////
void	CArea::CalcBBox( )
{
	m_AreaBBox.min.x = m_vpSegments[0]->bbox.min.x;
	m_AreaBBox.min.y = m_vpSegments[0]->bbox.min.y;
	m_AreaBBox.max.x = m_vpSegments[0]->bbox.max.x;
	m_AreaBBox.max.y = m_vpSegments[0]->bbox.max.y;
	for(unsigned int sIdx=1; sIdx<m_vpSegments.size(); sIdx++)
	{
		if( m_AreaBBox.min.x>m_vpSegments[sIdx]->bbox.min.x )
			m_AreaBBox.min.x = m_vpSegments[sIdx]->bbox.min.x;
		if( m_AreaBBox.min.y>m_vpSegments[sIdx]->bbox.min.y )
			m_AreaBBox.min.y = m_vpSegments[sIdx]->bbox.min.y;
		if( m_AreaBBox.max.x<m_vpSegments[sIdx]->bbox.max.x )
			m_AreaBBox.max.x = m_vpSegments[sIdx]->bbox.max.x;
		if( m_AreaBBox.max.y<m_vpSegments[sIdx]->bbox.max.y )
			m_AreaBBox.max.y = m_vpSegments[sIdx]->bbox.max.y;
	}
}

//////////////////////////////////////////////////////////////////////////
void	CArea::AddEntity( const EntityId entId )
{
	//if(GetEntitySystem()->EntitiesUseGUIDs() && m_vEntityGuid.size() != m_vEntityID.size() + 1)
	//{
	//	EntityWarning( "CArea::AddEntity Failed, Use guid to add entity to areas" );
	//	return;
	//}
	m_vEntityID.push_back( entId );
	m_bAttachedSoundTested = false;
	// invalidating effect radius
	m_fMaximumEffectRadius = -1.0f;

	m_pAreaManager->SetAreasDirty();
}

void CArea::AddEntity( const EntityGUID entGuid )
{
	m_vEntityGuid.push_back( entGuid );
	EntityId entId = GetEntitySystem()->FindEntityByGuid(entGuid);
	AddEntity(entId);
}

void CArea::ResolveEntityIds()
{
	if(m_bEntityIdsResolved)
		return;

	for(unsigned int i = 0; i < m_vEntityGuid.size(); i++)
	{
		EntityId entId = GetEntitySystem()->FindEntityByGuid(m_vEntityGuid[i]);
		m_vEntityID[i] = entId;
	}

	m_bEntityIdsResolved = true;
}

float CArea::GetMaximumEffectRadius()
{
	if (m_fMaximumEffectRadius < 0.0f || gEnv->IsEditor())
	{
		// recalculate
		m_fMaximumEffectRadius = 0.0f;
		unsigned int nSize = m_vEntityID.size();
		for( unsigned int eIdx=0; eIdx<nSize; eIdx++ )
		{
			IEntity *pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]);
			if (pEntity)
			{
				IEntitySoundProxy *pSoundProxy = (IEntitySoundProxy*)pEntity->GetProxy(ENTITY_PROXY_SOUND);
				if (pSoundProxy)
				{
					m_fMaximumEffectRadius = max(m_fMaximumEffectRadius, pSoundProxy->GetEffectRadius());
				}
			}
		}
	}

		return m_fMaximumEffectRadius;

}
 	


//////////////////////////////////////////////////////////////////////////
bool	CArea::HasSoundAttached( )
{
	if (m_vEntityID.empty())
		return false;

	if (!m_bAttachedSoundTested)
	{
		for( unsigned int eIdx=0; eIdx<m_vEntityID.size(); eIdx++ )
		{
			IEntity* pAreaAttachedEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]);
			//		ASSERT(pEntity);
			if (pAreaAttachedEntity)
			{
				IEntityClass *pEntityClass = pAreaAttachedEntity->GetClass();
				string sClassName = pEntityClass->GetName();
				if (sClassName == "AmbientVolume" || sClassName == "SoundSpot" || sClassName == "ReverbVolume")
					m_bHasSoundAttached = true;
			}
		}
		m_bAttachedSoundTested = true;
	}

	return m_bHasSoundAttached;
}

//////////////////////////////////////////////////////////////////////////
void CArea::GetBBox(Vec2& vMin, Vec2& vMax) const
{
	// Only valid for shape areas.
	vMin = Vec2(m_AreaBBox.min.x, m_AreaBBox.min.y);
	vMax = Vec2(m_AreaBBox.max.x, m_AreaBBox.max.y);
}

//////////////////////////////////////////////////////////////////////////
void	CArea::AddEntites( const std::vector<EntityId> &entIDs )
{
	for(unsigned int i=0; i<entIDs.size(); i++)
		AddEntity(entIDs[i]);

	// invalidating effect radius
	m_fMaximumEffectRadius = -1.0f;
}

//////////////////////////////////////////////////////////////////////////
void	CArea::ClearEntities( )
{
	// tell all attached entities they have been disconnect to prevent lost entities
	IEntity* pEntity;

	if (CVar::pDrawAreaDebug->GetIVal() == 2)
		CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "DETACH_THIS");

	for( unsigned int eIdx=0; eIdx<m_vEntityID.size(); eIdx++ )
	{
		pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]);
		//		ASSERT(pEntity);
		if (pEntity)
		{
			SEntityEvent event;
			event.event = ENTITY_EVENT_DETACH_THIS;
			event.nParam[0] = m_EntityID;
			event.nParam[1] = m_AreaID;
			event.nParam[2] = 0;
			pEntity->SendEvent( event );
		}
	}

	m_vEntityID.clear();
	m_PrevFade = -1.0f;
	m_bHasSoundAttached = false;
	
	// invalidating effect radius
	m_fMaximumEffectRadius = -1.0f;
}

//////////////////////////////////////////////////////////////////////////
void CArea::AddCachedEvent(const SEntityEvent& event)
{
	m_cachedEvents.push_back(event);
}

void CArea::ClearCachedEventsFor(EntityId triggerId)
{
	for (CachedEvents::iterator it = m_cachedEvents.begin(); it != m_cachedEvents.end(); )
	{
		SEntityEvent& event = *it;

		if (event.nParam[0] == triggerId)
		{
			it = m_cachedEvents.erase(it);
			continue;
		}

		++it;
	}
}

void CArea::ClearCachedEvents()
{
	m_cachedEvents.clear();
}

void CArea::SendCachedEventsFor(EntityId entityID)
{
	if (!m_bInitialized || m_cachedEvents.empty())
		return;

	for (size_t i = 0; i < m_cachedEvents.size(); ++i)
	{
		SEntityEvent cachedEvent = m_cachedEvents[i]; // copy to avoid invalidation if vector re-allocates

		if (cachedEvent.nParam[0] != entityID)
			continue;

		if (CVar::pDrawAreaDebug->GetIVal() == 2)
		{
			string sState;
			if (cachedEvent.event == ENTITY_EVENT_ENTERNEARAREA)
				sState = "ENTERNEAR";
			if (cachedEvent.event == ENTITY_EVENT_MOVENEARAREA)
				sState = "MOVENEAR";
			if (cachedEvent.event == ENTITY_EVENT_ENTERAREA)
				sState = "ENTER";
			if (cachedEvent.event == ENTITY_EVENT_MOVEINSIDEAREA)
				sState = "MOVEINSIDE";
			if (cachedEvent.event == ENTITY_EVENT_LEAVEAREA)
				sState = "LEAVE";
			if (cachedEvent.event == ENTITY_EVENT_LEAVENEARAREA)
				sState = "LEAVENEAR";

			CryLog("<AreaManager> Area %d Queued Event: %s", m_EntityID, sState.c_str());
		}

		m_pAreaManager->OnEvent( cachedEvent.event, (EntityId)cachedEvent.nParam[0], this);

		cachedEvent.nParam[1] = m_AreaID;
		cachedEvent.nParam[2] = m_EntityID;
		cachedEvent.fParam[0] = m_PrevFade;

		for( unsigned int eIdx=0; eIdx<m_vEntityID.size(); eIdx++ )
		{
			if (IEntity* pAreaAttachedEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]))
				pAreaAttachedEntity->SendEvent( cachedEvent );
		}
	}

	ClearCachedEventsFor(entityID);
}



// do enter area - player was outside, now is inside
// calls entity OnEnterArea which calls script OnEnterArea( player, AreaID )	
//////////////////////////////////////////////////////////////////////////
void CArea::EnterArea( IEntity const* const __restrict pEntity )
{
	if (!m_bInitialized)
		return;

	m_PrevFade = -1;
	m_bIsActive = true;

	if (CVar::pDrawAreaDebug->GetIVal() == 2)
		CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "ENTER");

	for( unsigned int eIdx=0; eIdx<m_vEntityID.size(); eIdx++ )
	{
		IEntity* const __restrict pAreaAttachedEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]);
		//		ASSERT(pEntity);
		if (pAreaAttachedEntity)
		{
			SEntityEvent event;
			event.event = ENTITY_EVENT_ENTERAREA;
			event.nParam[0] = pEntity->GetId();
			event.nParam[1] = m_AreaID;
			event.nParam[2] = m_EntityID;
			event.fParam[0] = 1.f; // fading is handled within the near areas.. we've entered the inner area... ensure fade is fully on.. this fixes any time we teleport immediately into a region rather than transfering across the near region
			pAreaAttachedEntity->SendEvent( event );

			ClearCachedEventsFor((EntityId)event.nParam[0]);
		}
	}
}


// do leave area - player was inside, now is outside
// calls entity OnLeaveArea which calls script OnLeaveArea( player, AreaID )	
//////////////////////////////////////////////////////////////////////////
void	CArea::LeaveArea( IEntity const* const __restrict pSrcEntity )
{
	if (!m_bInitialized)
		return;

	if (CVar::pDrawAreaDebug->GetIVal() == 2)
		CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "LEAVE");

	for( unsigned int eIdx=0; eIdx<m_vEntityID.size(); eIdx++ )
	{
		IEntity* const __restrict pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]);
		//		ASSERT(pEntity);
		if (pEntity)
		{
			SEntityEvent event;
			event.event = ENTITY_EVENT_LEAVEAREA;
			event.nParam[0] = pSrcEntity->GetId();
			event.nParam[1] = m_AreaID;
			event.nParam[2] = m_EntityID;
			pEntity->SendEvent( event );

			ClearCachedEventsFor((EntityId)event.nParam[0]);
		}
	}

	// If the entity currently leaving is the last one in the area, set the area as inactive
	if (m_pAreaManager->GetNumberOfPlayersInArea(this) <= 1)
	{
		m_PrevFade = -1;
		m_bIsActive = false;
	}
}

// do enter near area - entity was "far", now is "near"
// calls entity OnEnterNearArea which calls script OnEnterNearArea( entity(player), AreaID )	
//////////////////////////////////////////////////////////////////////////
void	CArea::EnterNearArea( IEntity const* const __restrict pSrcEntity, float const fDistanceSq )
{
	if (!m_bInitialized)
		return;

	if (CVar::pDrawAreaDebug->GetIVal() == 2)
		CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "ENTERNEAR");

	for( unsigned int eIdx=0; eIdx<m_vEntityID.size(); eIdx++ )
	{
		IEntity* const __restrict pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]);
		//		ASSERT(pEntity);
		if (pEntity)
		{
			SEntityEvent event;
			event.event = ENTITY_EVENT_ENTERNEARAREA;
			event.nParam[0] = pSrcEntity->GetId();
			event.nParam[1] = m_AreaID;
			event.nParam[2] = m_EntityID;
			pEntity->SendEvent( event );

			ClearCachedEventsFor((EntityId)event.nParam[0]);
		}
	}
	//m_PrevFade = -1;
	//m_bIsActive = true;
}

// do leave near area - entity was "near", now is "far"
// calls entity OnLeaveNearArea which calls script OnLeaveNearArea( entity(player), AreaID )	
//////////////////////////////////////////////////////////////////////////
void	CArea::LeaveNearArea( IEntity const* const __restrict pSrcEntity )
{
	if (!m_bInitialized)
		return;

	if (CVar::pDrawAreaDebug->GetIVal() == 2)
		CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "LEAVENEAR");

	for( unsigned int eIdx=0; eIdx<m_vEntityID.size(); eIdx++ )
	{
		IEntity* const __restrict pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]);
		//		ASSERT(pEntity);
		if (pEntity)
		{
			SEntityEvent event;
			event.event = ENTITY_EVENT_LEAVENEARAREA;
			event.nParam[0] = pSrcEntity->GetId();
			event.nParam[1] = m_AreaID;
			event.nParam[2] = m_EntityID;
			pEntity->SendEvent( event );

			ClearCachedEventsFor((EntityId)event.nParam[0]);
		}
	}
	//m_PrevFade = -1;
	//m_bIsActive = true;
}



//calculate distance to area - proceed fade. player is inside of the area
//////////////////////////////////////////////////////////////////////////
void	CArea::UpdateArea( const Vec3 &vPos, IEntity const* const pEntity )
{
	if (!m_bInitialized)
		return;

	Vec3 pos3D = vPos;
	ProceedFade( pEntity, CalculateFade( pos3D ) );
}


//calculate distance to area - proceed fade. player is inside of the area
//////////////////////////////////////////////////////////////////////////
void	CArea::UpdateAreaInside( IEntity const* const __restrict pSrcEntity )
{
	if (!m_bInitialized)
		return;

	if (CVar::pDrawAreaDebug->GetIVal() == 2)
		CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "MOVEINSIDE");

	for( unsigned int eIdx=0; eIdx<m_vEntityID.size(); eIdx++ )
	{
		IEntity* const __restrict pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]);
		//		ASSERT(pEntity);
		if (pEntity)
		{
			SEntityEvent event;
			event.event = ENTITY_EVENT_MOVEINSIDEAREA;
			event.nParam[0] = pSrcEntity->GetId();
			event.nParam[1] = m_AreaID;
			event.nParam[2] = m_EntityID;			
			event.fParam[0] = m_PrevFade;
			pEntity->SendEvent( event );
		}
	}
}

// pEntity moves in an area, which is controlled by an area with a higher priority
//////////////////////////////////////////////////////////////////////////
void	CArea::ExclusiveUpdateAreaInside( IEntity const* const __restrict pSrcEntity, EntityId const AreaHighEntityID, float const fadeValue )
{
	if (!m_bInitialized)
		return;

	if (CVar::pDrawAreaDebug->GetIVal() == 2)
		CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "MOVEINSIDE");

	for( unsigned int eIdx=0; eIdx<m_vEntityID.size(); eIdx++ )
	{
		IEntity* const __restrict pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]);
		//		ASSERT(pEntity);
		if (pEntity)
		{
			SEntityEvent event;
			event.event = ENTITY_EVENT_MOVEINSIDEAREA;
			event.nParam[0] = pSrcEntity->GetId();
			event.nParam[1] = m_AreaID;
			event.nParam[2] = m_EntityID;	// AreaLowEntityID
			event.nParam[3] = AreaHighEntityID;			
			event.fParam[0] = fadeValue;
			pEntity->SendEvent( event );
		}
	}
}

// pEntity moves near an area, which is controlled by an area with a higher priority
//////////////////////////////////////////////////////////////////////////
void	CArea::ExclusiveUpdateAreaNear( IEntity const* const __restrict pSrcEntity, EntityId const AreaHighEntityID, float const fadeValue )
{
	if (!m_bInitialized)
		return;

	if (CVar::pDrawAreaDebug->GetIVal() == 2)
		CryLogAlways("<AreaManager> Area %d Direct Event: %s", m_EntityID, "MOVENEAR");

	for( unsigned int eIdx=0; eIdx<m_vEntityID.size(); eIdx++ )
	{
		IEntity* const __restrict pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]);
		//		ASSERT(pEntity);
		if (pEntity)
		{
			SEntityEvent event;
			event.event = ENTITY_EVENT_MOVENEARAREA;
			event.nParam[0] = pSrcEntity->GetId();
			event.nParam[1] = m_AreaID;
			event.nParam[2] = m_EntityID;	// AreaLowEntityID
			event.nParam[3] = AreaHighEntityID;			
			event.fParam[0] = fadeValue;
			pEntity->SendEvent( event );
		}
	}
}

// Entity is "near" the area
//////////////////////////////////////////////////////////////////////////
void	CArea::UpdateAreaNear( IEntity const* const __restrict pSrcEntity, float const fDistanceSq )
{
	if (!m_bInitialized)
		return;

	if (CVar::pDrawAreaDebug->GetIVal() == 2)
		CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "MOVENEAR");

	for( unsigned int eIdx=0; eIdx<m_vEntityID.size(); eIdx++ )
	{
		IEntity* const __restrict pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]);
		//		ASSERT(pEntity);
		if (pEntity)
		{
			SEntityEvent event;
			event.event = ENTITY_EVENT_MOVENEARAREA;
			event.nParam[0] = pSrcEntity->GetId();
			event.nParam[1] = m_AreaID;
			event.nParam[2] = m_EntityID;
			event.fParam[0] = m_PrevFade;
			event.fParam[1] = fDistanceSq;
			pEntity->SendEvent( event );
		}
	}
}

//calculate distance to area. player is inside of the area 
//////////////////////////////////////////////////////////////////////////
float	CArea::CalculateFade( const Vec3& pos3D )
{
	if (!m_bInitialized)
		return 0;

	//	a2DPoint pos = *((CArea::a2DPoint*)(&pos3D));
	a2DPoint pos = CArea::a2DPoint(pos3D);
	float fadeCoeff = 0.0f;

	switch(m_AreaType)
	{
	case	 ENTITY_AREA_TYPE_SHAPE:
		fadeCoeff = CalcDistToPoint( pos );
		break;
	case	 ENTITY_AREA_TYPE_SPHERE:
		{
			if (m_fProximity <= 0.0f)
			{
				fadeCoeff = 1.0f;
				break;
			}
			Vec3 Delta = pos3D - m_SphereCenter;
			fadeCoeff = (m_SphereRadius - Delta.GetLength()) / m_fProximity;
			if (fadeCoeff > 1.0f)
				fadeCoeff = 1.0f;
			break;
		}
	case	 ENTITY_AREA_TYPE_BOX:
		{
			if (m_fProximity <= 0.0f)
			{
				fadeCoeff = 1.0f;
				break;
			}
			Vec3 p3D = m_InvMatrix.TransformPoint(pos3D);
			Vec3 MinDelta = p3D-m_BoxMin;
			Vec3 MaxDelta = m_BoxMax-p3D;
			Vec3 EdgeDist = (m_BoxMax - m_BoxMin) / 2.0f;
			if ((!EdgeDist.x) || (!EdgeDist.y) || (!EdgeDist.z))
			{
				fadeCoeff = 1.0f;
				break;
			}

			float fFadeScale = m_fProximity / 100.0f;
			EdgeDist *= fFadeScale;

			//float fFade=MinDelta.x/EdgeDist.x;
			float fMinFade = MinDelta.x / EdgeDist.x;					

			for (int k=0; k<3; k++)
			{
				float fFade1 = MinDelta[k] / EdgeDist[k];
				float fFade2 = MaxDelta[k] / EdgeDist[k];
				fMinFade = min(fMinFade, min(fFade1, fFade2));
			} //k

			/*
			fFade=MinDelta.y/EdgeDist.y;
			if (fFade<fMinFade)
			fMinFade=fFade;

			fFade=MinDelta.z/EdgeDist.z;
			if (fFade<fMinFade)
			fMinFade=fFade;

			fFade=MaxDelta.x/EdgeDist.x;
			if (fFade<fMinFade)
			fMinFade=fFade;

			fFade=MaxDelta.y/EdgeDist.y;
			if (fFade<fMinFade)
			fMinFade=fFade;

			fFade=MaxDelta.z/EdgeDist.z;
			if (fFade<fMinFade)
			fMinFade=fFade;
			*/

			fadeCoeff = fMinFade;
			if (fadeCoeff > 1.0f)
				fadeCoeff = 1.0f;
			break;
		}
	}

	return fadeCoeff;
}

//proceed fade. player is inside of the area
//////////////////////////////////////////////////////////////////////////
void	CArea::ProceedFade( IEntity const* const __restrict pSrcEntity, float const fadeCoeff )
{
	if (!m_bInitialized)
		return;

	// no update if fade coefficient hasn't changed
	if(m_PrevFade == fadeCoeff)
		return;
	m_PrevFade = fadeCoeff;

	if (CVar::pDrawAreaDebug->GetIVal() == 2)
		CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "MOVEINSIDE");

	for( unsigned int eIdx=0; eIdx<m_vEntityID.size(); eIdx++ )
	{
		IEntity* const __restrict pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]);
		//		ASSERT(pEntity);
		if (pEntity)
		{
			SEntityEvent event;
			event.event = ENTITY_EVENT_MOVEINSIDEAREA;
			event.nParam[0] = pSrcEntity->GetId();
			event.nParam[1] = m_EntityID;
			event.fParam[0] = fadeCoeff;
			pEntity->SendEvent( event );
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CArea::OnAreaCrossing(IEntity const* const __restrict pEntity)
{
	if (m_bInitialized && pEntity)
	{
		IEntity* __restrict pAreaAttachedEntity = NULL;
		std::vector<EntityId>::const_iterator const IterEnd(m_vEntityID.end());

		for (std::vector<EntityId>::const_iterator Iter(m_vEntityID.begin()); Iter != IterEnd; ++Iter)
		{
			pAreaAttachedEntity = GetEntitySystem()->GetEntity(*Iter);

			if (pAreaAttachedEntity)
			{
				SEntityEvent oEvent;
				oEvent.event     = ENTITY_EVENT_CROSS_AREA;
				oEvent.nParam[0] = pEntity->GetId();
				pAreaAttachedEntity->SendEvent(oEvent);
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void	CArea::Draw( const int idx) 
{
	I3DEngine *p3DEngine = gEnv->p3DEngine;
	IRenderAuxGeom *pRC = gEnv->pRenderer->GetIRenderAuxGeom();
	pRC->SetRenderFlags( e_Def3DPublicRenderflags );

	ColorB	colorsArray[] = {	ColorB(255,0,0,255),
		ColorB(0, 255, 0, 255),
		ColorB(0, 0, 255, 255),
		ColorB(255, 255, 0, 255),
		ColorB(255, 0, 255, 255),
		ColorB(0, 255, 255, 255),
		ColorB(255, 255, 255, 255),
	};
	ColorB	color = colorsArray[idx%(sizeof(colorsArray)/sizeof(ColorB))];
	ColorB	color1 = colorsArray[(idx+1)%(sizeof(colorsArray)/sizeof(ColorB))];
//	ColorB	color2 = colorsArray[(idx+2)%(sizeof(colorsArray)/sizeof(ColorB))];

	switch(m_AreaType)
	{
	case	 ENTITY_AREA_TYPE_SHAPE:
		{		
			Vec3	v0, v1;
			float	deltaZ = 0.1f;
			unsigned int nSize = m_vpSegments.size();
			for(unsigned int sIdx=0; sIdx<nSize; sIdx++)
			{
				if( m_vpSegments[sIdx]->k < 0 )
				{
					v0.x = m_vpSegments[sIdx]->bbox.min.x;
					v0.y = m_vpSegments[sIdx]->bbox.max.y;

					v1.x = m_vpSegments[sIdx]->bbox.max.x;
					v1.y = m_vpSegments[sIdx]->bbox.min.y;
				}
				else
				{
					v0.x = m_vpSegments[sIdx]->bbox.min.x;
					v0.y = m_vpSegments[sIdx]->bbox.min.y;

					v1.x = m_vpSegments[sIdx]->bbox.max.x;
					v1.y = m_vpSegments[sIdx]->bbox.max.y;
				}

				v0.z = max(m_VOrigin, p3DEngine->GetTerrainElevation(v0.x, v0.y) + deltaZ);
				v1.z = max(m_VOrigin, p3DEngine->GetTerrainElevation(v1.x, v1.y) + deltaZ);

				// draw lower line segments
				pRC->DrawLine( v0, color, v1, color);

				// Draw upper line segments and vertical edges
				if( m_VSize > 0.0f )
				{
					Vec3 v0Z = Vec3(v0.x, v0.y, m_VOrigin + m_VSize);
					Vec3 v1Z = Vec3(v1.x, v1.y, m_VOrigin + m_VSize);

					pRC->DrawLine( v0, color, v0Z, color );
					//pRC->DrawLine( v1, color, v1Z, color );
					pRC->DrawLine( v0Z, color, v1Z, color );

				}
			}
			break;
		}
	case	 ENTITY_AREA_TYPE_SPHERE:
		{
			ColorB  color3 = color;
			color3.a = 64;

			pRC->SetRenderFlags( e_Def3DPublicRenderflags|e_AlphaBlended );
			pRC->DrawSphere(m_SphereCenter, m_SphereRadius, color3);
			break;
		}
	case	 ENTITY_AREA_TYPE_BOX:
		{
			float fLength = m_BoxMax.x - m_BoxMin.x;
			float fWidth	= m_BoxMax.y - m_BoxMin.y;
			float fHeight = m_BoxMax.z - m_BoxMin.z;

			Vec3 v0 = m_BoxMin;
			Vec3 v1 = Vec3(m_BoxMin.x+fLength,	m_BoxMin.y,					m_BoxMin.z);
			Vec3 v2 = Vec3(m_BoxMin.x+fLength,	m_BoxMin.y+fWidth,	m_BoxMin.z);
			Vec3 v3 = Vec3(m_BoxMin.x,					m_BoxMin.y+fWidth,	m_BoxMin.z);
			Vec3 v4 = Vec3(m_BoxMin.x,					m_BoxMin.y,					m_BoxMin.z+fHeight);
			Vec3 v5 = Vec3(m_BoxMin.x+fLength,	m_BoxMin.y,					m_BoxMin.z+fHeight);
			Vec3 v6 = Vec3(m_BoxMin.x+fLength,	m_BoxMin.y+fWidth,	m_BoxMin.z+fHeight);
			Vec3 v7 = Vec3(m_BoxMin.x,					m_BoxMin.y+fWidth,	m_BoxMin.z+fHeight);

			v0 = m_WorldTM.TransformPoint(v0);
			v1 = m_WorldTM.TransformPoint(v1);
			v2 = m_WorldTM.TransformPoint(v2);
			v3 = m_WorldTM.TransformPoint(v3);
			v4 = m_WorldTM.TransformPoint(v4);
			v5 = m_WorldTM.TransformPoint(v5);
			v6 = m_WorldTM.TransformPoint(v6);
			v7 = m_WorldTM.TransformPoint(v7);

			// draw lower half of box
			pRC->DrawLine( v0, color1, v1 , color1);
			pRC->DrawLine( v1, color1, v2 , color1);
			pRC->DrawLine( v2, color1, v3 , color1);
			pRC->DrawLine( v3, color1, v0 , color1);

			// draw upper half of box
			pRC->DrawLine( v4, color1, v5 , color1);
			pRC->DrawLine( v5, color1, v6 , color1);
			pRC->DrawLine( v6, color1, v7 , color1);
			pRC->DrawLine( v7, color1, v4 , color1);

			// draw vertical edges
			pRC->DrawLine( v0, color1, v4 , color1);
			pRC->DrawLine( v1, color1, v5 , color1);
			pRC->DrawLine( v2, color1, v6 , color1);
			pRC->DrawLine( v3, color1, v7 , color1);

			break;
		}
	default:
		break;
	}
}

#include UNIQUE_VIRTUAL_WRAPPER(IArea)
